From 77ce39244d9fa340b90a3ac3b4ebeae78ffa1fc8 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 29 Jun 2020 23:21:00 +0200 Subject: [PATCH 01/30] Simplified API, refactoring --- fling-java-codestyle.xml | 754 +++++++++--------- service/fling/pom.xml | 429 +++++----- .../net/friedl/fling/FlingConfiguration.java | 14 +- .../fling/controller/ArtifactController.java | 72 +- .../fling/controller/FlingController.java | 76 +- .../friedl/fling/model/dto/ArtifactDto.java | 31 +- .../net/friedl/fling/model/dto/FlingDto.java | 103 +-- .../fling/model/dto/FlingSharingDto.java | 12 - .../fling/model/json/PathDeserializer.java | 34 + .../fling/model/json/PathSerializer.java | 26 + .../fling/model/mapper/ArtifactMapper.java | 44 +- .../fling/model/mapper/FlingMapper.java | 9 +- .../fling/persistence/archive/Archive.java | 40 - .../archive/impl/FileSystemArchive.java | 80 -- .../impl/FileSystemArchiveConfiguration.java | 41 - .../persistence/entities/ArtifactEntity.java | 35 +- .../persistence/entities/FlingEntity.java | 38 +- .../repositories/ArtifactRepository.java | 8 +- .../repositories/FlingRepository.java | 5 +- .../persistence/types/PathConverter.java | 21 + .../fling/security/AuthorizationService.java | 82 +- .../security/FlingWebSecurityConfigurer.java | 19 - .../AuthenticationController.java | 2 +- .../authentication/AuthenticationService.java | 15 +- .../authentication/GrantedFlingAuthority.java | 7 +- .../friedl/fling/service/ArtifactService.java | 102 ++- .../friedl/fling/service/FlingService.java | 204 ++--- .../fling/service/ServiceException.java | 42 + .../archive/ArchiveException.java | 4 +- .../fling/service/archive/ArchiveService.java | 50 ++ .../archive/impl/FileSystemArchive.java | 217 +++++ .../spring-configuration-metadata.json | 52 ++ .../src/main/resources/application-local.yml | 2 +- .../src/main/resources/application-prod.yml | 2 +- .../controller/ArtifactControllerTest.java | 72 +- .../friedl/fling/model/ArtifactDtoTest.java | 67 ++ .../net/friedl/fling/model/FlingDtoTest.java | 109 +++ .../fling/model/ModelTestConfiguration.java | 17 + 38 files changed, 1598 insertions(+), 1339 deletions(-) delete mode 100644 service/fling/src/main/java/net/friedl/fling/model/dto/FlingSharingDto.java create mode 100644 service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java create mode 100644 service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/persistence/archive/Archive.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchive.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchiveConfiguration.java create mode 100644 service/fling/src/main/java/net/friedl/fling/persistence/types/PathConverter.java create mode 100644 service/fling/src/main/java/net/friedl/fling/service/ServiceException.java rename service/fling/src/main/java/net/friedl/fling/{persistence => service}/archive/ArchiveException.java (96%) create mode 100644 service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java create mode 100644 service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java create mode 100644 service/fling/src/main/resources/META-INF/spring-configuration-metadata.json create mode 100644 service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/model/ModelTestConfiguration.java diff --git a/fling-java-codestyle.xml b/fling-java-codestyle.xml index a20c095..d8e97af 100644 --- a/fling-java-codestyle.xml +++ b/fling-java-codestyle.xml @@ -1,380 +1,380 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/service/fling/pom.xml b/service/fling/pom.xml index 9118ffe..72a7885 100644 --- a/service/fling/pom.xml +++ b/service/fling/pom.xml @@ -1,206 +1,247 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.2.6.RELEASE - - - net.friedl - fling - 0.0.1-SNAPSHOT - fling - Simple artifact sharing + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + net.friedl + fling + 0.1-SNAPSHOT + fling + Simple artifact sharing - - 11 - 1.3.1.Final - 1.64 - 0.11.1 - ${project.parent.version} - + + 11 + 1.3.1.Final + 1.64 + 0.11.1 + ${project.parent.version} + - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-actuator - + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-actuator + - - - io.jsonwebtoken - jjwt-api - ${jwt.version} - - - io.jsonwebtoken - jjwt-impl - ${jwt.version} - runtime - - - io.jsonwebtoken - jjwt-jackson - ${jwt.version} - runtime - + + + io.jsonwebtoken + jjwt-api + ${jwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jwt.version} + runtime + - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.projectlombok - lombok - true - - - org.mapstruct - mapstruct - ${mapstruct.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - provided - - + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.mapstruct + mapstruct + ${mapstruct.version} + - - - - - src/main/resources - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - - ${java.version} - ${java.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - org.projectlombok - lombok - ${lombok.version} - - - org.springframework.boot - spring-boot-configuration-processor - ${spring.version} - - - - - - maven-deploy-plugin - - - default-deploy - deploy - - deploy - - - - - - + + + org.springdoc + springdoc-openapi-ui + 1.2.32 + + - - - nexus-snapshots - https://nexus.friedl.net/repository/maven-snapshots/ - - + + + + + src/main/resources + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + - - - - local - - local - - jdt_apt - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - org.springframework.boot - spring-boot-configuration-processor - true - - - com.h2database - h2 - runtime - - - - - - prod - - prod - - - - com.h2database - h2 - runtime - - - - + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.version} + + + + + + + + org.springdoc + springdoc-openapi-maven-plugin + 1.0 + + + integration-test + + generate + + + + + flingapi.json + ${project.build.directory}/openapi-spec + + true + + + + + + maven-deploy-plugin + + + default-deploy + deploy + + deploy + + + + + + + + + + nexus-snapshots + https://nexus.friedl.net/repository/maven-snapshots/ + + + + + + + local + + local + + jdt_apt + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + com.h2database + h2 + runtime + + + + + + prod + + prod + + + + com.h2database + h2 + runtime + + + + diff --git a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java index 6f24df1..b710f87 100644 --- a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java @@ -1,5 +1,6 @@ package net.friedl.fling; +import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.springframework.context.annotation.Bean; @@ -8,7 +9,10 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import net.friedl.fling.model.json.PathDeserializer; +import net.friedl.fling.model.json.PathSerializer; @Configuration public class FlingConfiguration { @@ -16,15 +20,21 @@ public class FlingConfiguration { public MessageDigest keyHashDigest() throws NoSuchAlgorithmException { return MessageDigest.getInstance("SHA-512"); } - + @Bean public ObjectMapper objectMapper() { + SimpleModule simpleModule = new SimpleModule(); + simpleModule + .addDeserializer(Path.class, new PathDeserializer()) + .addSerializer(Path.class, new PathSerializer()); + ObjectMapper objectMapper = new ObjectMapper() .setSerializationInclusion(Include.NON_ABSENT) .registerModule(new JavaTimeModule()) // Handle instant as milliseconds .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false) - .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false); + .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) + .registerModule(simpleModule); return objectMapper; } diff --git a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java index 6cf61e1..68c252d 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java @@ -1,6 +1,8 @@ package net.friedl.fling.controller; -import java.util.List; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; @@ -10,73 +12,59 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.ArtifactDto; -import net.friedl.fling.persistence.archive.ArchiveException; import net.friedl.fling.service.ArtifactService; +import net.friedl.fling.service.archive.ArchiveService; +@Slf4j @RestController -@RequestMapping("/api") +@RequestMapping("/api/artifacts") public class ArtifactController { private ArtifactService artifactService; + private ArchiveService archiveService; @Autowired - public ArtifactController(ArtifactService artifactService) { + public ArtifactController(ArtifactService artifactService, ArchiveService archiveService) { this.artifactService = artifactService; + this.archiveService = archiveService; } - @GetMapping(path = "/artifacts", params = "flingId") - public List getArtifacts(@RequestParam Long flingId) { - return artifactService.findAllArtifacts(flingId); + @GetMapping(path = "/{id}") + public ArtifactDto getArtifact(@PathVariable UUID id) { + return artifactService.getById(id); } - @GetMapping(path = "/artifacts", params = "artifactId") - public ResponseEntity getArtifact(@RequestParam Long artifactId) { - return ResponseEntity.of(artifactService.findArtifact(artifactId)); + @DeleteMapping(path = "/{id}") + public void deleteArtifact(@PathVariable UUID id) { + artifactService.delete(id); } - @PostMapping("/artifacts/{flingId}") - public ArtifactDto postArtifact(@PathVariable Long flingId, HttpServletRequest request) - throws Exception { - return artifactService.storeArtifact(flingId, request.getInputStream()); + @PostMapping(path = "/{id}/data") + public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) { + try { + archiveService.storeArtifact(id, request.getInputStream()); + } catch (IOException e) { + log.error("Could not read input from stream", e); + throw new UncheckedIOException(e); + } } - @PatchMapping(path = "/artifacts/{artifactId}", consumes = MediaType.APPLICATION_JSON_VALUE) - public ArtifactDto patchArtifact(@PathVariable Long artifactId, @RequestBody String body) { - return artifactService.mergeArtifact(artifactId, body); - } - - @DeleteMapping(path = "/artifacts/{artifactId}") - public void deleteArtifact(@PathVariable Long artifactId) throws ArchiveException { - artifactService.deleteArtifact(artifactId); - } - - @GetMapping(path = "/artifacts/{artifactId}/downloadid") - public String getDownloadId(@PathVariable Long artifactId) { - return artifactService.generateDownloadId(artifactId); - } - - @GetMapping(path = "/artifacts/{artifactId}/{downloadId}/download") - public ResponseEntity downloadArtifact(@PathVariable Long artifactId, - @PathVariable String downloadId) - throws ArchiveException { - - var artifact = artifactService.findArtifact(artifactId).orElseThrow(); - var stream = new InputStreamResource(artifactService.downloadArtifact(downloadId)); + @GetMapping(path = "/{id}/data") + public ResponseEntity downloadArtifact(@PathVariable UUID id) { + ArtifactDto artifactDto = artifactService.getById(id); + InputStreamResource data = new InputStreamResource(archiveService.getArtifact(id)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, - "attachment;filename=\"" + artifact.getName() + "\"") - .contentLength(artifact.getSize()) + "attachment;filename=\"" + artifactDto.getPath().getFileName() + "\"") .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(stream); + .body(data); } } diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index de84211..b1e393f 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -1,7 +1,7 @@ package net.friedl.fling.controller; -import java.io.IOException; import java.util.List; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; @@ -12,79 +12,73 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.FlingDto; -import net.friedl.fling.persistence.archive.ArchiveException; +import net.friedl.fling.service.ArtifactService; import net.friedl.fling.service.FlingService; +import net.friedl.fling.service.archive.ArchiveService; @RestController -@RequestMapping("/api") +@RequestMapping("/api/fling") public class FlingController { private FlingService flingService; + private ArtifactService artifactService; + private ArchiveService archiveService; @Autowired - public FlingController(FlingService flingService) { + public FlingController(FlingService flingService, ArtifactService artifactService, + ArchiveService archiveService) { + this.flingService = flingService; + this.artifactService = artifactService; + this.archiveService = archiveService; } - @GetMapping("/fling") + @GetMapping public List getFlings() { return flingService.findAll(); } - @PostMapping("/fling") - public Long postFling(@RequestBody FlingDto flingDto) { - return flingService.createFling(flingDto); + @PostMapping + public FlingDto postFling(@RequestBody FlingDto flingDto) { + return flingService.create(flingDto); } - @PutMapping("/fling/{flingId}") - public void putFling(@PathVariable Long flingId, @RequestBody FlingDto flingDto) { - flingService.mergeFling(flingId, flingDto); + @PostMapping("/{id}/artifact") + public ArtifactDto postArtifact(@PathVariable UUID id, @RequestBody ArtifactDto artifactDto) { + return artifactService.create(id, artifactDto); } - @GetMapping(path = "/fling", params = "flingId") - public ResponseEntity getFling(@RequestParam Long flingId) { - return ResponseEntity.of(flingService.findFlingById(flingId)); + @GetMapping(path = "/{id}") + public FlingDto getFling(@PathVariable UUID id) { + return flingService.getById(id); } - @GetMapping(path = "/fling", params = "shareId") - public ResponseEntity getFlingByShareId(@RequestParam String shareId) { - return ResponseEntity.of(flingService.findFlingByShareId(shareId)); + @GetMapping(path = "/share/{shareId}") + public FlingDto getFlingByShareId(@PathVariable String shareId) { + return flingService.getByShareId(shareId); } - @GetMapping(path = "/fling/shareExists/{shareId}") - public Boolean getShareExists(@PathVariable String shareId) { - return flingService.existsShareUrl(shareId); + @DeleteMapping("/{id}") + public void deleteFling(@PathVariable UUID id) { + flingService.delete(id); } - @DeleteMapping("/fling/{flingId}") - public void deleteFling(@PathVariable Long flingId) { - flingService.deleteFlingById(flingId); - } - - @GetMapping(path = "/fling/{flingId}/package") - public String packageFling(@PathVariable Long flingId) throws IOException, ArchiveException { - return flingService.packageFling(flingId); - } - - @GetMapping(path = "/fling/{flingId}/download/{downloadId}") - public ResponseEntity downloadFling(@PathVariable Long flingId, - @PathVariable String downloadId) throws ArchiveException, IOException { - var fling = flingService.findFlingById(flingId).orElseThrow(); - var flingPackage = flingService.downloadFling(downloadId); - var stream = new InputStreamResource(flingPackage.getFirst()); + @GetMapping(path = "/{id}/data") + public ResponseEntity getFlingData(@PathVariable UUID id) { + FlingDto flingDto = flingService.getById(id); + InputStreamResource data = new InputStreamResource(archiveService.getFling(id)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, - "attachment;filename=\"" + fling.getName() + ".zip" + "\"") - .contentLength(flingPackage.getSecond()) + "attachment;filename=\"" + flingDto.getName() + ".zip" + "\"") + .contentLength(200L) // FIXME .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(stream); + .body(data); } } diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java index 0bd96f6..a2dba90 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java @@ -1,23 +1,30 @@ package net.friedl.fling.model.dto; +import java.nio.file.Path; import java.time.Instant; +import java.util.UUID; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@Builder +@NoArgsConstructor +@AllArgsConstructor public class ArtifactDto { - private String name; + @NotNull + private UUID id; - private Long id; + @NotNull + private Path path; + + @Builder.Default + private Instant uploadTime = Instant.now(); - private String path; + private String archiveId; - private String doi; - - private Long size; - - private Integer version; - - private Instant uploadTime; - - private FlingDto fling; + @Builder.Default + private Boolean archived = false; } diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java index 46df0bb..90d9111 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java @@ -1,96 +1,43 @@ package net.friedl.fling.model.dto; import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@Builder +@NoArgsConstructor +@AllArgsConstructor public class FlingDto { + @NotNull + private UUID id; + + @NotNull private String name; - private Long id; + @NotNull + @Builder.Default + private Instant creationTime = Instant.now(); - private Instant creationTime; - - @JsonIgnore - private Boolean directDownload; - - @JsonIgnore - private Boolean allowUpload; - - @JsonIgnore - private Boolean shared; - - @JsonIgnore - private String shareUrl; - - @JsonIgnore - private Integer expirationClicks; - - @JsonIgnore - private Instant expirationTime; + @NotNull + private String shareId; private String authCode; - @JsonProperty("sharing") - private void unpackSharing(Map sharing) { - this.directDownload = (Boolean) sharing.getOrDefault("directDownload", false); - this.allowUpload = (Boolean) sharing.getOrDefault("allowUpload", false); - this.shared = (Boolean) sharing.getOrDefault("shared", true); - this.shareUrl = (String) sharing.getOrDefault("shareUrl", null); - } + @Builder.Default + private Boolean directDownload = false; - @JsonProperty("sharing") - private Map packSharing() { - Map sharing = new HashMap<>(); - sharing.put("directDownload", this.directDownload); - sharing.put("allowUpload", this.allowUpload); - sharing.put("shared", this.shared); - sharing.put("shareUrl", this.shareUrl); + @Builder.Default + private Boolean allowUpload = false; - return sharing; - } + @Builder.Default + private Boolean shared = true; - @JsonProperty("expiration") - private void unpackExpiration(Map expiration) { - String type = (String) expiration.getOrDefault("type", null); - if (type == null) - return; + private Integer expirationClicks; - switch (type) { - case "time": - this.expirationClicks = null; - // json can only handle int, long must be given as string - // TODO: this back and forth conversion is a bit hack-ish - this.expirationTime = - Instant.ofEpochMilli(Long.valueOf(expiration.get("value").toString())); - break; - case "clicks": - this.expirationTime = null; - this.expirationClicks = Integer.valueOf(expiration.get("value").toString()); - break; - default: - throw new IllegalArgumentException("Unexpected value '" + type + "'"); - } - } - - @JsonProperty("expiration") - private Map packExpiration() { - Map expiration = new HashMap<>(); - - if (this.expirationClicks != null) { - expiration.put("type", "clicks"); - expiration.put("value", this.expirationClicks); - } - - if (this.expirationTime != null) { - expiration.put("type", "time"); - expiration.put("value", this.expirationTime.toEpochMilli()); - } - - return expiration; - } + private Instant expirationTime; } diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingSharingDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingSharingDto.java deleted file mode 100644 index fee9bb5..0000000 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingSharingDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.friedl.fling.model.dto; - -import lombok.Data; - -@Data -public class FlingSharingDto { - private Boolean allowUpload; - - private Boolean directDownload; - - private String shareUrl; -} diff --git a/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java b/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java new file mode 100644 index 0000000..be33cfd --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java @@ -0,0 +1,34 @@ +package net.friedl.fling.model.json; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +public class PathDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1504807365764537418L; + + public PathDeserializer() { + this(String.class); + } + + protected PathDeserializer(Class vc) { + super(vc); + } + + @Override + public Path deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + ObjectCodec codec = p.getCodec(); + JsonNode node = codec.readTree(p); + + return Paths.get(node.textValue()); + } + +} diff --git a/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java b/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java new file mode 100644 index 0000000..cc25022 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java @@ -0,0 +1,26 @@ +package net.friedl.fling.model.json; + +import java.io.IOException; +import java.nio.file.Path; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +public class PathSerializer extends StdSerializer { + + public PathSerializer() { + this(Path.class); + } + + protected PathSerializer(Class t) { + super(t); + } + + private static final long serialVersionUID = -1003917305429893614L; + + @Override + public void serialize(Path value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(value.toString()); + } + +} diff --git a/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java b/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java index 2a897eb..b9091a2 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java +++ b/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java @@ -1,49 +1,15 @@ package net.friedl.fling.model.mapper; -import java.lang.reflect.Field; import java.util.List; -import java.util.Map; -import java.util.Optional; import org.mapstruct.Mapper; -import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.persistence.entities.ArtifactEntity; -@Slf4j @Mapper(componentModel = "spring") -public abstract class ArtifactMapper { - public abstract ArtifactDto map(ArtifactEntity artifactEntity); +public interface ArtifactMapper { + ArtifactDto map(ArtifactEntity artifactEntity); + ArtifactEntity map(ArtifactDto artifactDto); - public abstract ArtifactEntity map(ArtifactDto artifactDto); - - public abstract List map(List artifactEntities); - - public Optional map(Optional artifactEntity) { - return artifactEntity.map(a -> map(a)); - } - - public ArtifactDto merge(ArtifactDto originalArtifactDto, Map patch) { - ArtifactDto mergedArtifactDto = new ArtifactDto(); - - for (Field field : ArtifactDto.class.getDeclaredFields()) { - String fieldName = field.getName(); - field.setAccessible(true); - try { - if (patch.containsKey(fieldName)) { - if (field.getType().equals(Long.class)) { - field.set(mergedArtifactDto, ((Number) patch.get(fieldName)).longValue()); - } - field.set(mergedArtifactDto, patch.get(fieldName)); - } else { - field.set(mergedArtifactDto, field.get(originalArtifactDto)); - } - } catch (IllegalArgumentException | IllegalAccessException e) { - log.error("Could not merge {} [value={}] with {}", fieldName, patch.get(fieldName), - originalArtifactDto, - e); - } - } - - return mergedArtifactDto; - } + List mapEntities(List artifactEntities); + List mapDtos(List artifactDtos); } diff --git a/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java b/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java index e7541b1..32b5184 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java +++ b/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java @@ -1,7 +1,6 @@ package net.friedl.fling.model.mapper; import java.util.List; -import java.util.Optional; import org.mapstruct.Mapper; import net.friedl.fling.model.dto.FlingDto; import net.friedl.fling.persistence.entities.FlingEntity; @@ -9,12 +8,8 @@ import net.friedl.fling.persistence.entities.FlingEntity; @Mapper(componentModel = "spring") public interface FlingMapper { FlingDto map(FlingEntity flingEntity); - - default Optional map(Optional flingEntity) { - return flingEntity.map(f -> map(f)); - } - FlingEntity map(FlingDto flingDto); - List map(List flingEntities); + List mapEntities(List flingEntities); + List mapDtos(List flingDtos); } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/archive/Archive.java b/service/fling/src/main/java/net/friedl/fling/persistence/archive/Archive.java deleted file mode 100644 index e4790f8..0000000 --- a/service/fling/src/main/java/net/friedl/fling/persistence/archive/Archive.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.friedl.fling.persistence.archive; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface Archive { - /** - * Retrieve an artifact from the archive - * - * @param id The unique artifact id as returned by {@link Archive#store} - * @return An {@link InputStream} for reading the artifact - */ - InputStream get(String id) throws ArchiveException; - - /** - * Store an artifact - * - * @param is The artifact represented as {@link InputStream} - * @return A unique archive id for the artifact - * @throws IOException If anything goes wrong while storing the artifact in the archive - */ - String store(InputStream is) throws ArchiveException; - - default String store(File file) throws ArchiveException { - try { - return store(new FileInputStream(file)); - } catch (IOException ex) { - throw new ArchiveException(ex); - } - } - - /** - * Delete an artifact - * - * @param id The unique artifact id as returned by {@link Archive#store} - */ - void remove(String id) throws ArchiveException; -} diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchive.java b/service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchive.java deleted file mode 100644 index 9630f09..0000000 --- a/service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchive.java +++ /dev/null @@ -1,80 +0,0 @@ -package net.friedl.fling.persistence.archive.impl; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import net.friedl.fling.persistence.archive.Archive; -import net.friedl.fling.persistence.archive.ArchiveException; - -@Component("fileSystemArchive") -public class FileSystemArchive implements Archive { - private MessageDigest fileStoreDigest; - - private FileSystemArchiveConfiguration configuration; - - @Autowired - public FileSystemArchive(MessageDigest fileStoreDigest, - FileSystemArchiveConfiguration configuration) { - this.fileStoreDigest = fileStoreDigest; - this.configuration = configuration; - } - - @Override - public InputStream get(String id) throws ArchiveException { - try { - var path = Paths.get(configuration.getDirectory(), id); - FileInputStream fis = new FileInputStream(path.toFile()); - return fis; - } catch (FileNotFoundException ex) { - throw new ArchiveException(ex); - } - } - - @Override - public String store(InputStream is) throws ArchiveException { - try { - byte[] fileBytes = is.readAllBytes(); - is.close(); - - String fileStoreId = hexEncode(fileStoreDigest.digest(fileBytes)); - - FileChannel fc = FileChannel.open(Paths.get(configuration.getDirectory(), fileStoreId), - StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE, - StandardOpenOption.CREATE); - - fc.write(ByteBuffer.wrap(fileBytes)); - - fc.close(); - return fileStoreId; - - } catch (IOException ex) { - throw new ArchiveException(ex); - } - } - - @Override - public void remove(String id) throws ArchiveException { - var path = Paths.get(configuration.getDirectory(), id); - try { - Files.deleteIfExists(path); - } catch (IOException e) { - throw new ArchiveException("Could not delete file at " + path.toString(), e); - } - } - - private String hexEncode(byte[] fileStoreId) { - StringBuilder sb = new StringBuilder(fileStoreId.length * 2); - for (byte b : fileStoreId) - sb.append(String.format("%02x", b)); - return sb.toString(); - } -} diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchiveConfiguration.java b/service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchiveConfiguration.java deleted file mode 100644 index 7140857..0000000 --- a/service/fling/src/main/java/net/friedl/fling/persistence/archive/impl/FileSystemArchiveConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.friedl.fling.persistence.archive.impl; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import javax.annotation.PostConstruct; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Configuration -@ConfigurationProperties("fling.archive.fileystem") -@ConditionalOnBean(FileSystemArchive.class) -@Getter -@Setter -public class FileSystemArchiveConfiguration { - private String directory; - - @Bean - public MessageDigest fileStoreDigest() throws NoSuchAlgorithmException { - return MessageDigest.getInstance("SHA-512"); - } - - @PostConstruct - public void init() throws IOException { - if (directory == null) { - log.info("Directory not configured take temp path"); - Path tmpPath = Files.createTempDirectory("fling"); - this.directory = tmpPath.toAbsolutePath().toString(); - } - - log.info("File store directory: {}", directory); - } -} diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java index 224edd2..8cf5dca 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java @@ -1,46 +1,37 @@ package net.friedl.fling.persistence.entities; +import java.nio.file.Path; import java.time.Instant; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; -import javax.persistence.PrePersist; import javax.persistence.Table; import lombok.Getter; import lombok.Setter; @Entity @Table(name = "Artifact") -@Getter -@Setter +@Getter @Setter public class ArtifactEntity { @Id @GeneratedValue - private Long id; + private UUID id; - private String name; + @Column(nullable = false) + private Path path; - private Integer version; + @Column(nullable = false) + private Instant uploadTime = Instant.now(); + + @Column(unique = true, nullable = true) + private String archiveId; - private String path; - - @Column(unique = true) - private String doi; - - private Instant uploadTime; - - private Long size; + @Column(nullable = false) + private Boolean archived = false; @ManyToOne(optional = false) private FlingEntity fling; - - @PrePersist - private void prePersist() { - this.uploadTime = Instant.now(); - - if (this.version == null) - this.version = -1; - } } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java index a8dfdf3..afcd94d 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java @@ -2,69 +2,47 @@ package net.friedl.fling.persistence.entities; import java.time.Instant; import java.util.Set; +import java.util.UUID; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; -import javax.persistence.PostPersist; -import javax.persistence.PrePersist; import javax.persistence.Table; import lombok.Getter; import lombok.Setter; @Entity @Table(name = "Fling") -@Getter -@Setter +@Getter @Setter public class FlingEntity { @Id @GeneratedValue - private Long id; + private UUID id; private String name; - private Instant creationTime; + private Instant creationTime = Instant.now(); private Instant expirationTime; private Integer expirationClicks; @Column(nullable = false) - private Boolean directDownload; + private Boolean directDownload = false; @Column(nullable = false) - private Boolean allowUpload; + private Boolean allowUpload = false; @Column(nullable = false) - private Boolean shared; + private Boolean shared = true; @Column(unique = true, nullable = false) - private String shareUrl; + private String shareId; private String authCode; @OneToMany(mappedBy = "fling", cascade = CascadeType.ALL, orphanRemoval = true) private Set artifacts; - - @PrePersist - private void prePersist() { - if (this.directDownload == null) - this.directDownload = false; - if (this.allowUpload == null) - this.allowUpload = false; - if (this.shared == null) - this.shared = true; - - this.creationTime = Instant.now(); - } - - @PostPersist - private void postPersist() { - System.out.println("ID: " + this.id); - System.out.println("Share Url: " + this.shareUrl); - - this.shareUrl = this.id + this.shareUrl; - } } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java index 684e15e..a34e438 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java @@ -1,14 +1,10 @@ package net.friedl.fling.persistence.repositories; import java.util.List; -import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import net.friedl.fling.persistence.entities.ArtifactEntity; -public interface ArtifactRepository extends JpaRepository { - Optional findByDoi(String doi); - - List deleteByDoi(String doi); - +public interface ArtifactRepository extends JpaRepository { List findAllByFlingId(Long flingId); } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java index 9e5c5ca..59d2248 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java @@ -1,14 +1,15 @@ package net.friedl.fling.persistence.repositories; import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import net.friedl.fling.persistence.entities.FlingEntity; -public interface FlingRepository extends JpaRepository { +public interface FlingRepository extends JpaRepository { Optional findByName(String name); - Optional findByShareUrl(String shareUrl); + FlingEntity findByShareId(String shareId); @Query("SELECT COUNT(*) FROM ArtifactEntity a, FlingEntity f where a.fling=f.id and f.id=:flingId") Long countArtifactsById(Long flingId); diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/types/PathConverter.java b/service/fling/src/main/java/net/friedl/fling/persistence/types/PathConverter.java new file mode 100644 index 0000000..cdd9c2b --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/persistence/types/PathConverter.java @@ -0,0 +1,21 @@ +package net.friedl.fling.persistence.types; + +import java.nio.file.Path; +import java.nio.file.Paths; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +@Converter(autoApply = true) +public class PathConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(Path attribute) { + return attribute.toString(); + } + + @Override + public Path convertToEntityAttribute(String dbData) { + return Paths.get(dbData); + } + +} diff --git a/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java index 12396b4..05cf99c 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java @@ -1,89 +1,71 @@ package net.friedl.fling.security; -import java.util.NoSuchElementException; -import javax.servlet.http.HttpServletRequest; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; import net.friedl.fling.security.authentication.FlingToken; import net.friedl.fling.security.authentication.dto.UserAuthDto; -import net.friedl.fling.service.ArtifactService; import net.friedl.fling.service.FlingService; @Slf4j @Service public class AuthorizationService { private FlingService flingService; - private ArtifactService artifactService; @Autowired - public AuthorizationService(FlingService flingService, ArtifactService artifactService) { + public AuthorizationService(FlingService flingService) { this.flingService = flingService; - this.artifactService = artifactService; } - public boolean allowUpload(Long flingId, AbstractAuthenticationToken token) { - if (!(token instanceof FlingToken)) + public boolean allowUpload(UUID flingId, AbstractAuthenticationToken token) { + if (!(token instanceof FlingToken)) { + log.warn("Authorization attempt without fling token for {}. Authorization denied.", flingId); return false; + } FlingToken flingToken = (FlingToken) token; - if (flingToken.getGrantedFlingAuthority().getAuthority() - .equals(FlingAuthority.FLING_OWNER.name())) { + if (FlingAuthority.FLING_OWNER.name() + .equals(flingToken.getGrantedFlingAuthority().getAuthority())) { + log.debug("Owner authorized for upload [id = {}]", flingId); return true; } - var uploadAllowed = flingService.findFlingById(flingId).orElseThrow().getAllowUpload(); + boolean uploadAllowed = flingService.getById(flingId).getAllowUpload(); + boolean authorized = uploadAllowed + && flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); - return uploadAllowed && flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); + log.debug("User {} authorized for upload [id = {}]", authorized ? "" : "not", flingId); + + return authorized; } - public boolean allowPatchingArtifact(Long artifactId, FlingToken authentication) { - var flingId = artifactService.findArtifact(artifactId).orElseThrow().getFling().getId(); - return allowUpload(flingId, authentication); - } - - public boolean allowFlingAccess(UserAuthDto userAuth, String shareUrl) { - return userAuth.getShareId().equals(shareUrl); - } - - public boolean allowFlingAccess(Long flingId, AbstractAuthenticationToken token) { - if (!(token instanceof FlingToken)) + public boolean allowFlingAccess(UUID flingId, AbstractAuthenticationToken token) { + if (!(token instanceof FlingToken)) { + log.warn("Authorization attempt without fling token for {}. Authorization denied.", flingId); return false; + } FlingToken flingToken = (FlingToken) token; - if (flingToken.getGrantedFlingAuthority().getAuthority() - .equals(FlingAuthority.FLING_OWNER.name())) { + if (FlingAuthority.FLING_OWNER.name() + .equals(flingToken.getGrantedFlingAuthority().getAuthority())) { + log.debug("Owner authorized for fling access [id = {}]", flingId); return true; } - return flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); + boolean authorized = flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); + log.debug("User {} authorized for fling access [id = {}]", authorized ? "" : "not", flingId); + + return authorized; } - public boolean allowFlingAccess(AbstractAuthenticationToken token, HttpServletRequest request) { - if (!(token instanceof FlingToken)) - return false; + public boolean allowFlingAccess(UserAuthDto userAuth, String shareId) { + boolean authorized = userAuth.getShareId().equals(shareId); + log.debug("User {} authorized for fling access [shareId = {}]", authorized ? "" : "not", + shareId); - FlingToken flingToken = (FlingToken) token; - if (flingToken.getGrantedFlingAuthority().getAuthority() - .equals(FlingAuthority.FLING_OWNER.name())) { - return true; - } - - var shareId = request.getParameter("shareId"); - - Long flingId; - - try { - flingId = shareId != null - ? flingService.findFlingByShareId(shareId).orElseThrow().getId() - : Long.parseLong(request.getParameter("flingId")); - } catch (NumberFormatException | NoSuchElementException e) { - log.warn("Invalid shareId [shareId=\"{}\"] or flingId [flingId=\"{}\"] found", - request.getParameter("shareId"), request.getParameter("flingId")); - flingId = null; - } - - return flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); + return authorized; } + } diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index 76ee797..69580f4 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -1,9 +1,7 @@ package net.friedl.fling.security; import static org.springframework.security.config.Customizer.withDefaults; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,9 +10,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -101,20 +96,6 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { //@formatter:on } - private RequestMatcher modificationMethodsAntMatcher(String antPattern) { - return multiMethodAntMatcher(antPattern, - HttpMethod.PATCH, HttpMethod.PUT, - HttpMethod.POST, HttpMethod.DELETE); - } - - private RequestMatcher multiMethodAntMatcher(String antPattern, HttpMethod... httpMethods) { - List antMatchers = Arrays.stream(httpMethods) - .map(m -> new AntPathRequestMatcher(antPattern, m.toString())) - .collect(Collectors.toList()); - - return new OrRequestMatcher(antMatchers); - } - @Bean public CorsConfigurationSource corsConfigurationSource() { // see https://stackoverflow.com/a/43559266 diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java index 42b82f5..2604a59 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java @@ -19,7 +19,7 @@ public class AuthenticationController { this.authenticationService = authenticationService; } - @PostMapping("/auth/owner") + @PostMapping(path = "/auth/owner") public String authenticateOwner(@RequestBody OwnerAuthDto ownerAuthDto) { return authenticationService.authenticate(ownerAuthDto); } diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java index f3828c8..a8bc181 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java @@ -3,6 +3,7 @@ package net.friedl.fling.security.authentication; import java.security.Key; import java.time.Instant; import java.util.Date; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.BadCredentialsException; @@ -12,6 +13,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; +import net.friedl.fling.model.dto.FlingDto; import net.friedl.fling.security.FlingAuthority; import net.friedl.fling.security.FlingSecurityConfiguration; import net.friedl.fling.security.authentication.dto.OwnerAuthDto; @@ -49,17 +51,16 @@ public class AuthenticationService { } public String authenticate(UserAuthDto userAuth) { - var fling = flingService.findFlingByShareId(userAuth.getShareId()) - .orElseThrow(); + FlingDto flingDto = flingService.getByShareId(userAuth.getShareId()); String authCode = userAuth.getCode(); - if (!flingService.hasAuthCode(fling.getId(), authCode)) { + if (!flingService.validateAuthCode(flingDto.getId(), authCode)) { throw new AccessDeniedException("Wrong fling code"); } return makeBaseBuilder() .setSubject("user") - .claim("sid", fling.getShareUrl()) + .claim("sid", flingDto.getShareId()) .compact(); } @@ -68,7 +69,7 @@ public class AuthenticationService { Claims claims = parseClaims(token); FlingAuthority authority; - Long flingId; + UUID flingId; switch (claims.getSubject()) { case "owner": @@ -77,8 +78,8 @@ public class AuthenticationService { break; case "user": authority = FlingAuthority.FLING_USER; - var sid = claims.get("sid", String.class); - flingId = flingService.findFlingByShareId(sid).orElseThrow().getId(); + String sid = claims.get("sid", String.class); + flingId = flingService.getByShareId(sid).getId(); break; default: throw new BadCredentialsException("Invalid token"); diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java index 15d7c36..a56462f 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java @@ -1,5 +1,6 @@ package net.friedl.fling.security.authentication; +import java.util.UUID; import org.springframework.security.core.GrantedAuthority; import net.friedl.fling.security.FlingAuthority; @@ -13,14 +14,14 @@ public class GrantedFlingAuthority implements GrantedAuthority { private static final long serialVersionUID = -1552301479158714777L; private FlingAuthority authority; - private Long flingId; + private UUID flingId; - public GrantedFlingAuthority(FlingAuthority authority, Long flingId) { + public GrantedFlingAuthority(FlingAuthority authority, UUID flingId) { this.authority = authority; this.flingId = flingId; } - public Long getFlingId() { + public UUID getFlingId() { return this.flingId; } diff --git a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java index 412d5fa..4a35965 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java @@ -1,89 +1,85 @@ package net.friedl.fling.service; -import java.io.InputStream; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.UUID; import javax.transaction.Transactional; +import javax.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.json.JsonParser; -import org.springframework.boot.json.JsonParserFactory; import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.mapper.ArtifactMapper; -import net.friedl.fling.persistence.archive.Archive; -import net.friedl.fling.persistence.archive.ArchiveException; import net.friedl.fling.persistence.entities.ArtifactEntity; +import net.friedl.fling.persistence.entities.FlingEntity; import net.friedl.fling.persistence.repositories.ArtifactRepository; import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.service.archive.ArchiveService; +@Slf4j @Service @Transactional public class ArtifactService { - private FlingRepository flingRepository; private ArtifactRepository artifactRepository; + private FlingRepository flingRepository; private ArtifactMapper artifactMapper; - private Archive archive; + private ArchiveService archiveService; @Autowired public ArtifactService(ArtifactRepository artifactRepository, FlingRepository flingRepository, - ArtifactMapper artifactMapper, Archive archive) { + ArtifactMapper artifactMapper, ArchiveService archiveService) { + this.artifactRepository = artifactRepository; this.flingRepository = flingRepository; this.artifactMapper = artifactMapper; - this.archive = archive; + this.archiveService = archiveService; } - public List findAllArtifacts(Long flingId) { - return artifactMapper.map(artifactRepository.findAllByFlingId(flingId)); + /** + * Fetch an {@link ArtifactDto} by id. Must be called with a valid artifact id, otherwise bails + * out with a {@link RuntimeException}. Synchronization must be done on client side. + * + * @param id A valid {@link UUID} for an existing entity in the database. Not null. + * @return The ArtifactDto corresponding to the {@code id} + */ + @NotNull + public ArtifactDto getById(@NotNull UUID id) { + return artifactMapper.map(artifactRepository.getOne(id)); } - public ArtifactDto storeArtifact(Long flingId, InputStream artifact) throws ArchiveException { - var flingEntity = flingRepository.findById(flingId).orElseThrow(); - var archiveId = archive.store(artifact); + /** + * Create a new {@link ArtifactEntity} from {@code artifactDto} for the fling {@code flingId}. + * + * @param flingId Id of an existing {@link FlingEntity} + * @param artifactDto The data for the new {@link ArtifactEntity} + * @return The newly created artifact + */ + public ArtifactDto create(UUID flingId, ArtifactDto artifactDto) { + FlingEntity flingEntity = flingRepository.getOne(flingId); - ArtifactEntity artifactEntity = new ArtifactEntity(); - artifactEntity.setDoi(archiveId); + ArtifactEntity artifactEntity = artifactMapper.map(artifactDto); artifactEntity.setFling(flingEntity); - - artifactRepository.save(artifactEntity); - + artifactEntity = artifactRepository.save(artifactEntity); return artifactMapper.map(artifactEntity); } + + /** + * Deletes an artifact identified by {@code id}. NOOP if the artifact cannot be found. + * + * @param id An {@link UUID} that identifies the artifact + */ + public void delete(UUID id) { + if (id == null) + return; - public Optional findArtifact(Long artifactId) { - return artifactMapper.map(artifactRepository.findById(artifactId)); - } + ArtifactEntity artifactEntity = artifactRepository.findById(id).orElse(null); - public ArtifactDto mergeArtifact(Long artifactId, String body) { - JsonParser jsonParser = JsonParserFactory.getJsonParser(); - Map parsedBody = jsonParser.parseMap(body); + if (artifactEntity == null) { + log.warn("Cannot delete artifact {}. Artifact not found.", id); + return; + } - artifactRepository.findById(artifactId) - // map entity to dto - .map(artifactMapper::map) - // merge parsedBody into dto - .map(a -> artifactMapper.merge(a, parsedBody)) - // map dto to entity - .map(artifactMapper::map) - .ifPresent(artifactRepository::save); - - return artifactMapper.map(artifactRepository.getOne(artifactId)); - } - - public void deleteArtifact(Long artifactId) throws ArchiveException { - var doi = artifactRepository.getOne(artifactId).getDoi(); - artifactRepository.deleteById(artifactId); - archive.remove(doi); - } - - public String generateDownloadId(Long artifactId) { - // TODO: This id is not secured! Generate temporary download id - return artifactRepository.getOne(artifactId).getDoi(); - } - - public InputStream downloadArtifact(String downloadId) throws ArchiveException { - return archive.get(downloadId); + archiveService.deleteArtifact(id); + artifactRepository.delete(artifactEntity); + log.info("Deleted artifact {}", artifactEntity); } } diff --git a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java index f346bf3..9cce26e 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java @@ -1,24 +1,11 @@ package net.friedl.fling.service; -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; import java.security.MessageDigest; import java.util.Base64; import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.zip.Deflater; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; +import java.util.UUID; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.util.Pair; import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.stereotype.Service; @@ -26,11 +13,9 @@ import org.springframework.util.StringUtils; import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.FlingDto; import net.friedl.fling.model.mapper.FlingMapper; -import net.friedl.fling.persistence.archive.Archive; -import net.friedl.fling.persistence.archive.ArchiveException; -import net.friedl.fling.persistence.entities.ArtifactEntity; import net.friedl.fling.persistence.entities.FlingEntity; import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.service.archive.ArchiveService; @Slf4j @Service @@ -39,135 +24,99 @@ public class FlingService { private FlingRepository flingRepository; private FlingMapper flingMapper; - private Archive archive; + private ArchiveService archiveService; private MessageDigest keyHashDigest; @Autowired - public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, Archive archive, + public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, + ArchiveService archiveService, MessageDigest keyHashDigest) { + this.flingRepository = flingRepository; this.flingMapper = flingMapper; - this.archive = archive; + this.archiveService = archiveService; this.keyHashDigest = keyHashDigest; } + /** + * Retrieves a list of all flings + * + * @return A list of all flings + */ public List findAll() { - return flingMapper.map(flingRepository.findAll()); + return flingMapper.mapEntities(flingRepository.findAll()); } - public Long createFling(FlingDto flingDto) { - if (!StringUtils.hasText(flingDto.getShareUrl())) { - flingDto.setShareUrl(generateShareUrl()); + /** + * Get a fling by id + * + * @param id Id of the fling. Must exist. + * @return The fling + */ + public FlingDto getById(UUID id) { + return flingMapper.map(flingRepository.getOne(id)); + } + + /** + * Creates a new fling entity from {@code flingDto} + * + * @param flingDto Base data from which the new fling should be created + * @return The created fling + */ + public FlingDto create(FlingDto flingDto) { + log.debug("Creating new fling"); + FlingEntity flingEntity = flingMapper.map(flingDto); + + if (!StringUtils.hasText(flingEntity.getShareId())) { + log.debug("No share id set. Generating random share id"); + flingEntity.setShareId(generateShareId()); + } + + if (StringUtils.hasText(flingEntity.getAuthCode())) { + log.debug("Hashing authentication code for {}", flingEntity.getId()); + flingEntity.setAuthCode(hashAuthCode(flingDto.getAuthCode())); } - var flingEntity = flingMapper.map(flingDto); - flingEntity.setAuthCode(hashKey(flingEntity.getAuthCode())); flingEntity = flingRepository.save(flingEntity); - return flingEntity.getId(); + log.debug("Created new fling {}", flingEntity.getId()); + return flingMapper.map(flingEntity); } - public Boolean existsShareUrl(String shareUrl) { - return !flingRepository.findByShareUrl(shareUrl).isEmpty(); + public FlingDto getByShareId(String shareId) { + FlingEntity flingEntity = flingRepository.findByShareId(shareId); + return flingMapper.map(flingEntity); } - public void mergeFling(Long flingId, FlingDto flingDto) { - var flingEntity = flingRepository.getOne(flingId); - - mergeNonEmpty(flingDto::getAllowUpload, flingEntity::setAllowUpload); - mergeNonEmpty(flingDto::getDirectDownload, flingEntity::setDirectDownload); - mergeWithEmpty(flingDto::getExpirationClicks, flingEntity::setExpirationClicks); - mergeWithEmpty(flingDto::getExpirationTime, flingEntity::setExpirationTime); - mergeNonEmpty(flingDto::getName, flingEntity::setName); - mergeNonEmpty(flingDto::getShared, flingEntity::setShared); - mergeNonEmpty(flingDto::getShareUrl, flingEntity::setShareUrl); - mergeWithEmpty(() -> hashKey(flingDto.getAuthCode()), flingEntity::setAuthCode); + public void delete(UUID id) { + archiveService.deleteFling(id); + flingRepository.deleteById(id); + log.debug("Deleted fling {}", id); } - public Optional findFlingById(Long flingId) { - return flingMapper.map(flingRepository.findById(flingId)); + public boolean validateAuthCode(UUID id, String authCode) { + FlingEntity flingEntity = flingRepository.getOne(id); + boolean valid = flingEntity.getAuthCode().equals(hashAuthCode(authCode)); + log.debug("Provided authentication for {} is {} valid", id, valid ? "" : "not"); + return valid; } - public Optional findFlingByShareId(String shareUrl) { - return flingMapper.map(flingRepository.findByShareUrl(shareUrl)); + private String hashAuthCode(String authCode) { + String hash = new String(Hex.encode(keyHashDigest.digest(authCode.getBytes()))); + log.debug("Hashed authentication code to {}", hash); + return hash; } - public void deleteFlingById(Long flingId) { - flingRepository.deleteById(flingId); - } - - public boolean hasAuthCode(Long flingId, String authCode) { - var fling = flingRepository.getOne(flingId); - - if (!StringUtils.hasText(fling.getAuthCode())) - return true; - - return fling.getAuthCode().equals(hashKey(authCode)); - } - - public String getShareName(String shareUrl) { - - FlingEntity flingEntity = flingRepository.findByShareUrl(shareUrl).orElseThrow(); - - if (flingEntity.getArtifacts().size() > 1) - return flingEntity.getName(); - else if (flingEntity.getArtifacts().size() == 1) - return flingEntity.getArtifacts().stream().findFirst().get().getName(); - - return null; - } - - public Long countArtifacts(Long flingId) { - return flingRepository.countArtifactsById(flingId); - } - - public Long getFlingSize(Long flingId) { - var fling = flingRepository.getOne(flingId); - - return fling.getArtifacts().stream() - .map(ae -> ae.getSize()) - .reduce(0L, (acc, as) -> acc + as); - } - - public String packageFling(Long flingId) throws IOException, ArchiveException { - var fling = flingRepository.getOne(flingId); - var tempFile = Files.createTempFile(Long.toString(flingId), ".zip"); - - try (var zipStream = new ZipOutputStream(new FileOutputStream(tempFile.toFile()))) { - zipStream.setLevel(Deflater.BEST_SPEED); - for (ArtifactEntity artifactEntity : fling.getArtifacts()) { - ZipEntry ze = new ZipEntry(artifactEntity.getName()); - zipStream.putNextEntry(ze); - - var artifactStream = archive.get(artifactEntity.getDoi()); - try (var archiveEntryStream = new BufferedInputStream(artifactStream)) { - int b; - while ((b = archiveEntryStream.read()) != -1) { - zipStream.write(b); - } - } finally { - zipStream.closeEntry(); - } - } - } - - return tempFile.getFileName().toString(); - } - - public Pair downloadFling(String fileId) throws IOException, ArchiveException { - var tempFile = Paths.get(System.getProperty("java.io.tmpdir"), fileId).toFile(); - - var archiveLength = tempFile.length(); - var archiveStream = new FileInputStream(tempFile); - - return Pair.of(archiveStream, archiveLength); - } - - public String generateShareUrl() { - var key = KeyGenerators + /** + * Generates a URL safe share id + * + * @return A random URL safe share id + */ + private String generateShareId() { + byte[] key = KeyGenerators .secureRandom(16) .generateKey(); - return Base64.getUrlEncoder().encodeToString(key) + String shareId = Base64.getUrlEncoder().encodeToString(key) // replace all special chars [=-_] in RFC 4648 // "URL and Filename safe" table with characters from // [A-Za-z0-9]. Hence, the generated share url will only consist @@ -175,23 +124,8 @@ public class FlingService { .replace('=', 'q') .replace('_', 'u') .replace('-', 'd'); - } - public String hashKey(String key) { - if (!StringUtils.hasText(key)) - return null; - - return new String(Hex.encode(keyHashDigest.digest(key.getBytes()))); - } - - private void mergeNonEmpty(Supplier sup, Consumer con) { - T r = sup.get(); - if (r != null) - con.accept(r); - } - - private void mergeWithEmpty(Supplier sup, Consumer con) { - T r = sup.get(); - con.accept(r); + log.debug("Generated share id {}", shareId); + return shareId; } } diff --git a/service/fling/src/main/java/net/friedl/fling/service/ServiceException.java b/service/fling/src/main/java/net/friedl/fling/service/ServiceException.java new file mode 100644 index 0000000..90abbf2 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/service/ServiceException.java @@ -0,0 +1,42 @@ +package net.friedl.fling.service; + +public class ServiceException extends Exception { + private static final long serialVersionUID = 2159182914434903969L; + + /** + * {@inheritDoc} + */ + public ServiceException() { + super(); + } + + /** + * {@inheritDoc} + */ + public ServiceException(String message) { + super(message); + } + + /** + * {@inheritDoc} + */ + public ServiceException(String message, Throwable cause) { + super(message, cause); + } + + /** + * {@inheritDoc} + */ + public ServiceException(Throwable cause) { + super(cause); + } + + /** + * {@inheritDoc} + */ + protected ServiceException(String message, Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/archive/ArchiveException.java b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java similarity index 96% rename from service/fling/src/main/java/net/friedl/fling/persistence/archive/ArchiveException.java rename to service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java index 46db704..fda5fd6 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/archive/ArchiveException.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java @@ -1,6 +1,6 @@ -package net.friedl.fling.persistence.archive; +package net.friedl.fling.service.archive; -public class ArchiveException extends Exception { +public class ArchiveException extends RuntimeException { private static final long serialVersionUID = 6216735865308056261L; /** diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java new file mode 100644 index 0000000..922bed9 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java @@ -0,0 +1,50 @@ +package net.friedl.fling.service.archive; + +import java.io.InputStream; +import java.util.UUID; +import java.util.zip.ZipInputStream; + +/** + * Interface for persisting artifacts + * + * @author Armin Friedl + */ +public interface ArchiveService { + /** + * Retrieve an artifact from the archive + * + * @param id The artifact id + * @return An {@link InputStream} for reading the artifact + */ + InputStream getArtifact(UUID artifactId); + + /** + * Retrieve a packaged fling from the archive + * + * @param flingId The fling id + * @return An {@link ZipInputStream} representing the fling and its artifacts + */ + ZipInputStream getFling(UUID flingId); + + /** + * Store an artifact + * + * @param artifactStream The artifact to store represented as {@link InputStream} + * @param artifactId The id of the artifact. Must be an existing artifact in the DB. Not null. + */ + void storeArtifact(UUID artifactId, InputStream artifactStream); + + /** + * Delete an artifact + * + * @param id The unique artifact id + */ + void deleteArtifact(UUID artifactId); + + /** + * Delete a fling + * + * @param flingId The unique fling id + */ + void deleteFling(UUID flingId); +} diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java new file mode 100644 index 0000000..ee72eca --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java @@ -0,0 +1,217 @@ +package net.friedl.fling.service.archive.impl; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.zip.ZipInputStream; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.transaction.Transactional; +import javax.validation.constraints.NotBlank; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Service; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.persistence.entities.ArtifactEntity; +import net.friedl.fling.persistence.repositories.ArtifactRepository; +import net.friedl.fling.service.archive.ArchiveService; + +@Slf4j +@Service +@ConfigurationProperties("fling.archive.filesystem") +@Transactional +public class FileSystemArchive implements ArchiveService { + @NotBlank + private Path archivePath; + + private ArtifactRepository artifactRepository; + + private Map filesystems; + + public FileSystemArchive(ArtifactRepository artifactRepository) { + this.artifactRepository = artifactRepository; + this.filesystems = new HashMap<>(); + } + + @PostConstruct + public void postConstruct() { + try { + Files.createDirectories(archivePath); + } catch (IOException e) { + log.error("Could not create directory at archive path {}", archivePath); + throw new UncheckedIOException(e); + } + } + + @PreDestroy + public void preDestroy() { + filesystems.forEach((uri, zfs) -> { + try { + zfs.close(); + } catch (IOException e) { + log.error("Could not close file system for {}", uri); + } + }); + } + + @Override + @SneakyThrows + public InputStream getArtifact(UUID artifactId) { + log.debug("Reading data for artifact {}", artifactId); + + FileSystem zipDisk = getZipDisk(artifactId); + return zipDisk.provider().newInputStream(getZipDiskPath(artifactId, zipDisk), + StandardOpenOption.READ); + + // do not close zip disk here or the input stream will be closed as well + } + + @Override + @SneakyThrows + public ZipInputStream getFling(UUID flingId) { + log.debug("Reading data for fling {}", flingId); + Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); + log.debug("Zip disk path is {}", zipDiskPath); + return new ZipInputStream(new FileInputStream(zipDiskPath.toFile())); + } + + @Override + @SneakyThrows + public void storeArtifact(UUID artifactId, InputStream artifactStream) { + log.debug("Storing artifact {}", artifactId); + + setArchived(artifactId, false); + FileSystem zipDisk = getZipDisk(artifactId); + Files.copy(artifactStream, getZipDiskPath(artifactId, zipDisk), + StandardCopyOption.REPLACE_EXISTING); + + // we need to close the zipDisk in order to flush it to disk + closeZipDisk(artifactId); + setArchived(artifactId, true); + } + + @Override + @SneakyThrows + public void deleteArtifact(UUID artifactId) { + log.debug("Deleting artifact {}", artifactId); + FileSystem zipDisk = getZipDisk(artifactId); + Files.delete(getZipDiskPath(artifactId, zipDisk)); + + // we need to close the zipDisk in order to flush it to disk + closeZipDisk(artifactId); + setArchived(artifactId, false); + } + + @Override + @SneakyThrows + public void deleteFling(UUID flingId) { + URI zipDiskUri = resolveFlingUri(flingId); + + log.debug("Closing zip disk at {}", zipDiskUri); + + // make sure nobody opens the filesystem while it is being closed and deleted + synchronized (filesystems) { + FileSystem zipDisk = filesystems.remove(zipDiskUri); + + if (zipDisk != null) { + zipDisk.close(); + log.debug("Zip disk closed"); + } else { + log.debug("No open zip disk found"); + } + + Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); + log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath); + Files.delete(zipDiskPath); + } + + } + + private void setArchived(UUID artifactId, boolean archived) { + ArtifactEntity artifactEntity = artifactRepository.getOne(artifactId); + artifactEntity.setArchived(archived); + + log.debug("Artifact[.id={}] set to {} archived", artifactId, archived ? "" : "not"); + } + + private Path getZipDiskPath(UUID artifactId, FileSystem zipDisk) { + ArtifactEntity artifactEntity = artifactRepository.getOne(artifactId); + log.debug("Getting zip disk path for {}", artifactEntity.getPath()); + + Path zipDiskPath = zipDisk.getPath(artifactEntity.getPath().toString()); + if (zipDiskPath.getParent() != null && !Files.exists(zipDiskPath.getParent())) { + try { + Files.createDirectories(zipDiskPath.getParent()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + log.debug("Got zip disk path {}", zipDiskPath); + return zipDiskPath; + } + + private FileSystem getZipDisk(UUID artifactId) throws IOException { + log.debug("Retrieving zip disk for artifact {}", artifactId); + URI uri = resolveArtifactUri(artifactId); + log.debug("Looking for zip disk at uri {}", uri); + + // make sure nobody opens closes, deletes or interleavingly opens the filesystem while it is + // being opened + synchronized (filesystems) { + if (!filesystems.containsKey(uri)) { + log.debug("Zip disk does not exist. Creating zip disk for {}", uri); + FileSystem zipDisk = FileSystems.newFileSystem(uri, Map.of("create", "true")); + filesystems.put(uri, zipDisk); + } + + return filesystems.get(uri); + } + } + + private void closeZipDisk(UUID artifactId) throws IOException { + log.debug("Closing zip disk for artifact {}", artifactId); + URI uri = resolveArtifactUri(artifactId); + log.debug("Closing zip disk at uri {}", uri); + + // make sure nobody opens the filesystem while it is being closed + synchronized (filesystems) { + FileSystem zipDisk = filesystems.remove(uri); + if (zipDisk == null) { + log.warn("Could not close zip disk at {}. Filesystem not found.", uri); + return; + } + + zipDisk.close(); + } + + } + + private URI resolveArtifactUri(UUID artifactId) throws IOException { + ArtifactEntity artifactEntity = artifactRepository.getOne(artifactId); + UUID flingId = artifactEntity.getFling().getId(); + + return resolveFlingUri(flingId); + } + + private URI resolveFlingUri(UUID flingId) throws IOException { + Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); + return URI.create("jar:file:" + zipDiskPath.toFile().getCanonicalPath()); + } + + public void setArchivePath(String archivePath) { + this.archivePath = Paths.get(archivePath); + } +} diff --git a/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json b/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 0000000..e629422 --- /dev/null +++ b/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json @@ -0,0 +1,52 @@ + { + "groups": [ + { + "name": "fling.archive.filesystem", + "type": "net.friedl.fling.service.archive.impl.FileSystemArchive", + "sourceType": "net.friedl.fling.service.archive.impl.FileSystemArchive" + }, + { + "name": "fling.security", + "type": "net.friedl.fling.security.FlingWebSecurityConfiguration", + "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + } + ], + "properties": [ + { + "name": "fling.archive.filesystem.archive-path", + "type": "java.lang.String", + "description": "Directory where FileSystemArchive stores its data", + "sourceType": "net.friedl.fling.service.archive.impl.FileSystemArchive" + }, + { + "name": "fling.security.allowed-origins", + "type": "java.util.List", + "description": "Allowed origins for CORS", + "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + }, + { + "name": "fling.security.admin-user", + "type": "java.lang.String", + "description": "Username of the admin user/instance owner", + "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + }, + { + "name": "fling.security.admin-password", + "type": "java.util.String", + "description": "Password of the admin user/instance owner", + "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + }, + { + "name": "fling.security.signing-key", + "type": "java.util.String", + "description": "Key for signing JWT tokens. Must be 256 bits (32 bytes)", + "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + }, + { + "name": "fling.security.jwt-expiration", + "type": "java.util.Long", + "description": "Time until JWT tokens expire", + "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + } + ] +} \ No newline at end of file diff --git a/service/fling/src/main/resources/application-local.yml b/service/fling/src/main/resources/application-local.yml index 8135d43..acddbaa 100644 --- a/service/fling/src/main/resources/application-local.yml +++ b/service/fling/src/main/resources/application-local.yml @@ -21,7 +21,7 @@ logging.level: # spring.http.log-request-details: true fling: - archive.fileystem.directory: "/home/armin/Desktop/fling" + archive.filesystem.archive-path: /home/armin/Desktop/fling security: allowed-origins: - "https://friedl.net" diff --git a/service/fling/src/main/resources/application-prod.yml b/service/fling/src/main/resources/application-prod.yml index d5eec04..edea956 100644 --- a/service/fling/src/main/resources/application-prod.yml +++ b/service/fling/src/main/resources/application-prod.yml @@ -16,7 +16,7 @@ logging.level: root: WARN fling: - archive.fileystem.directory: "/var/fling/files" + archive.filesystem.archive-path: "/var/fling/files" security: allowed-origins: - "https://fling.friedl.net" diff --git a/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java index 4e5c442..32ef8b7 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java @@ -1,21 +1,9 @@ package net.friedl.fling.controller; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.hasSize; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.FilterType; -import org.springframework.test.web.servlet.MockMvc; -import net.friedl.fling.model.dto.ArtifactDto; -import net.friedl.fling.service.ArtifactService; @WebMvcTest(controllers = ArtifactController.class, // do auto-configure security @@ -23,34 +11,34 @@ import net.friedl.fling.service.ArtifactService; // do not try to create beans in security excludeFilters = @Filter(type = FilterType.REGEX, pattern = "net.friedl.fling.security.*")) class ArtifactControllerTest { - @Autowired - private MockMvc mvc; - - @MockBean - private ArtifactService artifactService; - - @Test - public void testGetArtifacts_noArtifacts_empty() throws Exception { - Long flingId = 123L; - - when(artifactService.findAllArtifacts(flingId)).thenReturn(List.of()); - - mvc.perform(get("/api/artifacts").param("flingId", flingId.toString())) - .andExpect(jsonPath("$", hasSize(0))); - } - - @Test - public void testGetArtifacts_hasArtifacts_allArtifacts() throws Exception { - Long flingId = 123L; - String artifactName = "TEST"; - - ArtifactDto artifactDto = new ArtifactDto(); - artifactDto.setName(artifactName); - - when(artifactService.findAllArtifacts(flingId)).thenReturn(List.of(artifactDto)); - - mvc.perform(get("/api/artifacts").param("flingId", flingId.toString())) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].name", equalTo(artifactName))); - } +// @Autowired +// private MockMvc mvc; +// +// @MockBean +// private ArtifactService artifactService; +// +// @Test +// public void testGetArtifacts_noArtifacts_empty() throws Exception { +//// Long flingId = 123L; +//// +//// when(artifactService.findAllArtifacts(flingId)).thenReturn(List.of()); +//// +//// mvc.perform(get("/api/artifacts").param("flingId", flingId.toString())) +//// .andExpect(jsonPath("$", hasSize(0))); +// } +// +// @Test +// public void testGetArtifacts_hasArtifacts_allArtifacts() throws Exception { +//// Long flingId = 123L; +//// String artifactName = "TEST"; +//// +//// ArtifactDto artifactDto = new ArtifactDto(); +//// artifactDto.setName(artifactName); +//// +//// when(artifactService.findAllArtifacts(flingId)).thenReturn(List.of(artifactDto)); +//// +//// mvc.perform(get("/api/artifacts").param("flingId", flingId.toString())) +//// .andExpect(jsonPath("$", hasSize(1))) +//// .andExpect(jsonPath("$[0].name", equalTo(artifactName))); +// } } diff --git a/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java b/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java new file mode 100644 index 0000000..394079d --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java @@ -0,0 +1,67 @@ +package net.friedl.fling.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.nio.file.Paths; +import java.util.Set; +import java.util.UUID; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.model.dto.ArtifactDto; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = ModelTestConfiguration.class) +public class ArtifactDtoTest { + + @Autowired + private Validator validator; + + @Test + void testSetId_null_validationFails() { + ArtifactDto artifactDto = ArtifactDto.builder() + .id(null) + .path(Paths.get("test")) + .build(); + + + Set> constraintViolations = validator.validate(artifactDto); + + assertThat(constraintViolations).hasSize(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("id"); + assertThat(violation.getMessage()).isEqualTo("must not be null"); + } + + @Test + void testSetPath_null_validationFails() { + ArtifactDto artifactDto = ArtifactDto.builder() + .id(new UUID(0L, 0L)) + .path(null) + .build(); + + + Set> constraintViolations = validator.validate(artifactDto); + + assertThat(constraintViolations).hasSize(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("path"); + assertThat(violation.getMessage()).isEqualTo("must not be null"); + } + + @Test + void testMandatoryFieldsSet_validationOk() { + ArtifactDto artifactDto = ArtifactDto.builder() + .id(new UUID(0L, 0L)) + .path(Paths.get("test")) + .build(); + + Set> constraintViolations = validator.validate(artifactDto); + assertTrue(constraintViolations.isEmpty()); + } + +} diff --git a/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java b/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java new file mode 100644 index 0000000..3029211 --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java @@ -0,0 +1,109 @@ +package net.friedl.fling.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Instant; +import java.util.Set; +import java.util.UUID; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.model.dto.FlingDto; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = ModelTestConfiguration.class) +public class FlingDtoTest { + + @Autowired + private Validator validator; + + @Test + void testSetId_null_validationFails() { + FlingDto flingDto = FlingDto.builder() + .id(null) + .name("test") + .creationTime(Instant.EPOCH) + .shareId("test") + .build(); + + + Set> constraintViolations = validator.validate(flingDto); + + assertThat(constraintViolations).hasSize(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("id"); + assertThat(violation.getMessage()).isEqualTo("must not be null"); + } + + @Test + void testSetName_null_validationFails() { + FlingDto flingDto = FlingDto.builder() + .id(new UUID(0L, 0L)) + .name(null) + .creationTime(Instant.EPOCH) + .shareId("test") + .build(); + + + Set> constraintViolations = validator.validate(flingDto); + + assertThat(constraintViolations).hasSize(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("name"); + assertThat(violation.getMessage()).isEqualTo("must not be null"); + } + + @Test + void testSetCreationTime_null_validationFails() { + FlingDto flingDto = FlingDto.builder() + .id(new UUID(0L, 0L)) + .name("test") + .creationTime(null) + .shareId("test") + .build(); + + + Set> constraintViolations = validator.validate(flingDto); + + assertThat(constraintViolations).hasSize(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("creationTime"); + assertThat(violation.getMessage()).isEqualTo("must not be null"); + } + + @Test + void testSetShareId_null_validationFails() { + FlingDto flingDto = FlingDto.builder() + .id(new UUID(0L, 0L)) + .name("test") + .creationTime(Instant.EPOCH) + .shareId(null) + .build(); + + + Set> constraintViolations = validator.validate(flingDto); + + assertThat(constraintViolations).hasSize(1); + ConstraintViolation violation = constraintViolations.iterator().next(); + assertThat(violation.getPropertyPath().toString()).isEqualTo("shareId"); + assertThat(violation.getMessage()).isEqualTo("must not be null"); + } + + @Test + void testSetAllManadatory_validationOk() { + FlingDto flingDto = FlingDto.builder() + .id(new UUID(0L, 0L)) + .name("test") + .creationTime(Instant.EPOCH) + .shareId("test") + .build(); + + + Set> constraintViolations = validator.validate(flingDto); + assertTrue(constraintViolations.isEmpty()); + } +} diff --git a/service/fling/src/test/java/net/friedl/fling/model/ModelTestConfiguration.java b/service/fling/src/test/java/net/friedl/fling/model/ModelTestConfiguration.java new file mode 100644 index 0000000..f2bda01 --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/model/ModelTestConfiguration.java @@ -0,0 +1,17 @@ +package net.friedl.fling.model; + +import javax.validation.Validator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +@Configuration +public class ModelTestConfiguration { + + @Bean + public Validator validator() { + LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); + localValidatorFactoryBean.afterPropertiesSet(); + return localValidatorFactoryBean; + } +} From 3a05120da4a95dbc620f625f7fea471aab161fc3 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Wed, 1 Jul 2020 20:52:56 +0200 Subject: [PATCH 02/30] API documentation --- .drone.yml | 2 +- service/fling/pom.xml | 9 ++++++-- .../fling/controller/ArtifactController.java | 15 ++++++++++++ .../fling/controller/FlingController.java | 12 ++++++++++ .../friedl/fling/model/dto/ArtifactDto.java | 14 ++++++++--- .../net/friedl/fling/model/dto/FlingDto.java | 16 +++++++++++++ .../security/FlingSecurityConfiguration.java | 2 +- .../AuthenticationController.java | 8 ++++--- .../authentication/dto/OwnerAuthDto.java | 2 ++ .../authentication/dto/UserAuthDto.java | 2 ++ .../spring-configuration-metadata.json | 23 +++++++++++++++++++ .../src/main/resources/application-local.yml | 6 ++++- .../fling/src/main/resources/application.yml | 4 +++- 13 files changed, 103 insertions(+), 12 deletions(-) diff --git a/.drone.yml b/.drone.yml index 8255a52..66bf451 100644 --- a/.drone.yml +++ b/.drone.yml @@ -46,7 +46,7 @@ steps: dockerfile: container/Dockerfile context: ./container repo: arminfriedl/fling - tags: latest + tags: dev when: branch: - master diff --git a/service/fling/pom.xml b/service/fling/pom.xml index 72a7885..de6f6e8 100644 --- a/service/fling/pom.xml +++ b/service/fling/pom.xml @@ -21,6 +21,7 @@ 1.64 0.11.1 ${project.parent.version} + ${project.version} @@ -102,8 +103,12 @@ - + src/main/resources true diff --git a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java index 68c252d..add9f4c 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java @@ -16,6 +16,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.service.ArtifactService; @@ -24,6 +30,7 @@ import net.friedl.fling.service.archive.ArchiveService; @Slf4j @RestController @RequestMapping("/api/artifacts") +@Tag(name = "artifact", description = "Operations on /api/artifacts") public class ArtifactController { private ArtifactService artifactService; @@ -45,6 +52,8 @@ public class ArtifactController { artifactService.delete(id); } + @Operation(requestBody = @RequestBody( + content = @Content(schema = @Schema(type = "string", format = "binary")))) @PostMapping(path = "/{id}/data") public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) { try { @@ -55,6 +64,12 @@ public class ArtifactController { } } + @Operation(responses = { + @ApiResponse(responseCode = "200", + content = @Content( + mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE, + schema = @Schema(type = "string", format = "binary"))) + }) @GetMapping(path = "/{id}/data") public ResponseEntity downloadArtifact(@PathVariable UUID id) { ArtifactDto artifactDto = artifactService.getById(id); diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index b1e393f..3e9c0bf 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -15,6 +15,11 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.FlingDto; import net.friedl.fling.service.ArtifactService; @@ -23,6 +28,7 @@ import net.friedl.fling.service.archive.ArchiveService; @RestController @RequestMapping("/api/fling") +@Tag(name = "fling", description = "Operations on /api/fling") public class FlingController { private FlingService flingService; @@ -68,6 +74,12 @@ public class FlingController { flingService.delete(id); } + @Operation(responses = { + @ApiResponse(responseCode = "200", + content = @Content( + mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE, + schema = @Schema(type = "string", format = "binary"))) + }) @GetMapping(path = "/{id}/data") public ResponseEntity getFlingData(@PathVariable UUID id) { FlingDto flingDto = flingService.getById(id); diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java index a2dba90..7f27957 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java @@ -4,6 +4,8 @@ import java.nio.file.Path; import java.time.Instant; import java.util.UUID; import javax.validation.constraints.NotNull; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.AccessMode; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,18 +15,24 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor +@Schema(name = "Artifact") public class ArtifactDto { + @Schema(accessMode = AccessMode.READ_ONLY, type = "string") @NotNull private UUID id; + @Schema(type = "string", + description = "Path of the artifact") @NotNull private Path path; - + + @Schema(type = "integer", format = "int64", + description = "Upload time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") @Builder.Default private Instant uploadTime = Instant.now(); - private String archiveId; - + @Schema(accessMode = AccessMode.READ_ONLY, type = "boolean", + description = "Whether the artifact was successfully persisted in the archive.") @Builder.Default private Boolean archived = false; } diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java index 90d9111..0403e0b 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java @@ -3,6 +3,8 @@ package net.friedl.fling.model.dto; import java.time.Instant; import java.util.UUID; import javax.validation.constraints.NotNull; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.AccessMode; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -12,32 +14,46 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor +@Schema(name = "Fling") public class FlingDto { + @Schema(accessMode = AccessMode.READ_ONLY, type = "string") @NotNull private UUID id; + @Schema(description = "Name of the fling") @NotNull private String name; + @Schema(type = "integer", format = "int64", + description = "Creation time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") @NotNull @Builder.Default private Instant creationTime = Instant.now(); + @Schema(description = "Share id of the fling. Used in the share link.") @NotNull private String shareId; + @Schema(description = "Authentication code for password protecting a fling.") private String authCode; + @Schema(description = "Whether users should be redirected to fling download when accessing the " + + "fling by share id") @Builder.Default private Boolean directDownload = false; + @Schema(description = "Allow uploads to the fling by users") @Builder.Default private Boolean allowUpload = false; + @Schema(description = "Whether the fling is accessible by users via the share id") @Builder.Default private Boolean shared = true; + @Schema(description = "How many clicks are left until the fling access by share id is disallowed") private Integer expirationClicks; + @Schema(type = "integer", format = "int64", + description = "Expiration time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") private Instant expirationTime; } diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java b/service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java index da2a866..ac75054 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java @@ -11,8 +11,8 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import lombok.Data; -@Configuration @Data +@Configuration @ConfigurationProperties("fling.security") public class FlingSecurityConfiguration { private List allowedOrigins; diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java index 2604a59..d9b1e30 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java @@ -5,11 +5,13 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.security.authentication.dto.OwnerAuthDto; import net.friedl.fling.security.authentication.dto.UserAuthDto; @RestController -@RequestMapping("/api") +@RequestMapping("/api/auth") +@Tag(name = "auth", description = "Operations on /api/auth") public class AuthenticationController { private AuthenticationService authenticationService; @@ -19,12 +21,12 @@ public class AuthenticationController { this.authenticationService = authenticationService; } - @PostMapping(path = "/auth/owner") + @PostMapping(path = "/owner") public String authenticateOwner(@RequestBody OwnerAuthDto ownerAuthDto) { return authenticationService.authenticate(ownerAuthDto); } - @PostMapping("/auth/user") + @PostMapping("/user") public String authenticateUser(@RequestBody UserAuthDto userAuthDto) { return authenticationService.authenticate(userAuthDto); } diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java index c4ee495..606562f 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java @@ -1,8 +1,10 @@ package net.friedl.fling.security.authentication.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data +@Schema(name = "OwnerAuth") public class OwnerAuthDto { private String username; private String password; diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java index 51b08bc..b4a8107 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java @@ -1,8 +1,10 @@ package net.friedl.fling.security.authentication.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data +@Schema(name = "UserAuth") public class UserAuthDto { String shareId; String code; diff --git a/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json b/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json index e629422..187790c 100644 --- a/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json +++ b/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json @@ -9,9 +9,32 @@ "name": "fling.security", "type": "net.friedl.fling.security.FlingWebSecurityConfiguration", "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" + }, + { + "name": "fling.api", + "type": "net.friedl.fling.controller.OpenApiConfiguration", + "sourceType": "net.friedl.fling.controller.OpenApiConfiguration" } ], "properties": [ + { + "name": "fling.api.version", + "type": "java.lang.String", + "description": "Fling API version", + "sourceType": "net.friedl.fling.controller.OpenApiConfiguration" + }, + { + "name": "fling.api.server-url", + "type": "java.lang.String", + "description": "Base URL for the fling api", + "sourceType": "net.friedl.fling.controller.OpenApiConfiguration" + }, + { + "name": "fling.api.server-description", + "type": "java.lang.String", + "description": "A description for the server to be shown in OAS", + "sourceType": "net.friedl.fling.controller.OpenApiConfiguration" + }, { "name": "fling.archive.filesystem.archive-path", "type": "java.lang.String", diff --git a/service/fling/src/main/resources/application-local.yml b/service/fling/src/main/resources/application-local.yml index acddbaa..c814456 100644 --- a/service/fling/src/main/resources/application-local.yml +++ b/service/fling/src/main/resources/application-local.yml @@ -31,4 +31,8 @@ fling: admin-user: "${FLING_ADMIN_USER:admin}" admin-password: "${FLING_ADMIN_PASSWORD:123}" signing-key: "${FLING_SIGNING_KEY:changeitchangeitchangeitchangeit}" - jwt-expiration: "${FLING_JWT_EXPIRATION:180000}" \ No newline at end of file + jwt-expiration: "${FLING_JWT_EXPIRATION:180000}" + api: + version: "0" + server-url: "http://localhost:8080" + server-description: "API server for dev" diff --git a/service/fling/src/main/resources/application.yml b/service/fling/src/main/resources/application.yml index 052a6fa..2a9c28f 100644 --- a/service/fling/src/main/resources/application.yml +++ b/service/fling/src/main/resources/application.yml @@ -1 +1,3 @@ -spring.profiles.active: "@spring.profiles.active@" # To be replaced by maven according to profile settings \ No newline at end of file + # To be replaced by maven +spring.profiles.active: "@spring.profiles.active@" +fling.api.version: "@fling.api.version@" \ No newline at end of file From 2b18120c0b5ba218924a30cb5fa0f8d255a7322c Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Wed, 1 Jul 2020 22:58:56 +0200 Subject: [PATCH 03/30] Try client generator --- .drone.yml | 14 +++- LICENSE | 9 +++ container/etc/nginx/conf.d/fling.conf | 34 +++++++++ .../controller/OpenApiConfiguration.java | 71 +++++++++++++++++++ 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 LICENSE create mode 100644 service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java diff --git a/.drone.yml b/.drone.yml index 66bf451..4be0d9c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,9 +47,17 @@ steps: context: ./container repo: arminfriedl/fling tags: dev - when: - branch: - - master + +- name: runservice + image: arminfriedl/fling:dev + pull: always + detach: true + +- name: generate-clients + image: openapitools/openapi-generator-cli + commands: + - sleep 35 + - java -jar /opt/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i http://runservice:3000/v3/api-docs -g python -o flingclient volumes: - name: m2-cache diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e53cba0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright © 2020 Armin Friedl + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/container/etc/nginx/conf.d/fling.conf b/container/etc/nginx/conf.d/fling.conf index 3651c7b..ffa2005 100644 --- a/container/etc/nginx/conf.d/fling.conf +++ b/container/etc/nginx/conf.d/fling.conf @@ -27,6 +27,40 @@ server { client_max_body_size 5G; } + # handle openapi requests by openapi servlet + location /v3/api-docs { + proxy_pass http://localhost:8080; + + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + + # Required for web sockets to function + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # handle openapi requests by openapi servlet + location /swagger-ui.html { + proxy_pass http://localhost:8080; + + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + + # Required for web sockets to function + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + # always respond with index.html for unknown paths # (routing is client side) location / { diff --git a/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java b/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java new file mode 100644 index 0000000..8bf33c9 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java @@ -0,0 +1,71 @@ +package net.friedl.fling.controller; + +import java.util.Optional; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.SecurityScheme.Type; +import io.swagger.v3.oas.models.servers.Server; +import lombok.Data; + +@Data +@Configuration +@ConfigurationProperties("fling.api") +public class OpenApiConfiguration { + + private String version; + private String serverUrl; + private String serverDescription; + + @Bean + public OpenAPI openApi() { + OpenAPI openApi = new OpenAPI() + .components(new Components() + .addSecuritySchemes("bearer", bearerScheme()) + .addSchemas("resource", new Schema() + .type("string").format("binary"))) + .info(apiInfo()); + + serverItem().ifPresent(openApi::addServersItem); + + return openApi; + } + + public SecurityScheme bearerScheme() { + return new SecurityScheme() + .name("bearerAuth") + .type(Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"); + } + + public Info apiInfo() { + return new Info() + .contact(new Contact() + .name("Armin Friedl") + .url("https://www.friedl.net") + .email("dev@friedl.net")) + .license(new License() + .name("The MIT License (MIT)")) + .title("The Fling API") + .description("Share file collections with expiration, protection and short urls") + .version(version); + } + + public Optional serverItem() { + if (serverUrl == null) return Optional.empty(); + + return Optional.of( + new Server() + .description(serverDescription) + .url(serverUrl)); + } +} \ No newline at end of file From a66b51e1e507d0df30b7da88171eb4b78de37ded Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 5 Jul 2020 18:57:19 +0200 Subject: [PATCH 04/30] Add unit tests --- .../net/friedl/fling/FlingConfiguration.java | 2 +- .../fling/controller/ArtifactController.java | 28 +- .../controller/CommonExceptionHandler.java | 45 +++ .../fling/controller/FlingController.java | 5 +- .../friedl/fling/model/dto/ArtifactDto.java | 2 +- .../repositories/ArtifactRepository.java | 2 +- .../friedl/fling/service/ArtifactService.java | 18 +- .../friedl/fling/service/FlingService.java | 7 +- .../fling/service/ServiceException.java | 42 --- .../service/archive/ArchiveException.java | 73 ----- .../fling/service/archive/ArchiveService.java | 14 +- .../archive/impl/FileSystemArchive.java | 31 +- .../controller/ArtifactControllerTest.java | 156 +++++++-- .../fling/controller/FlingControllerTest.java | 206 ++++++++++++ .../fling/service/ArtifactServiceTest.java | 137 ++++++++ .../fling/service/FlingServiceTest.java | 181 +++++++++++ .../archive/FileSystemArchiveTest.java | 300 ++++++++++++++++++ .../resources/filesystem/archive_path/.keep | 0 .../resources/filesystem/artifacts/artifact1 | 1 + .../resources/filesystem/artifacts/artifact2 | 1 + 20 files changed, 1045 insertions(+), 206 deletions(-) create mode 100644 service/fling/src/main/java/net/friedl/fling/controller/CommonExceptionHandler.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/service/ServiceException.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java create mode 100644 service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java create mode 100644 service/fling/src/test/resources/filesystem/archive_path/.keep create mode 100644 service/fling/src/test/resources/filesystem/artifacts/artifact1 create mode 100644 service/fling/src/test/resources/filesystem/artifacts/artifact2 diff --git a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java index b710f87..7f97420 100644 --- a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java @@ -20,7 +20,7 @@ public class FlingConfiguration { public MessageDigest keyHashDigest() throws NoSuchAlgorithmException { return MessageDigest.getInstance("SHA-512"); } - + @Bean public ObjectMapper objectMapper() { SimpleModule simpleModule = new SimpleModule(); diff --git a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java index add9f4c..15f1c4d 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java @@ -1,7 +1,6 @@ package net.friedl.fling.controller; import java.io.IOException; -import java.io.UncheckedIOException; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; @@ -16,18 +15,15 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.service.ArtifactService; import net.friedl.fling.service.archive.ArchiveService; -@Slf4j @RestController @RequestMapping("/api/artifacts") @Tag(name = "artifact", description = "Operations on /api/artifacts") @@ -42,36 +38,28 @@ public class ArtifactController { this.archiveService = archiveService; } + @ApiResponse(responseCode = "404", description = "No artifact with `id` found") @GetMapping(path = "/{id}") public ArtifactDto getArtifact(@PathVariable UUID id) { return artifactService.getById(id); } + @ApiResponse(responseCode = "404", description = "No artifact with `id` found") @DeleteMapping(path = "/{id}") - public void deleteArtifact(@PathVariable UUID id) { + public void deleteArtifact(@PathVariable UUID id) throws IOException { artifactService.delete(id); } - @Operation(requestBody = @RequestBody( - content = @Content(schema = @Schema(type = "string", format = "binary")))) + @RequestBody(content = @Content(schema = @Schema(type = "string", format = "binary"))) @PostMapping(path = "/{id}/data") - public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) { - try { + public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) throws IOException { archiveService.storeArtifact(id, request.getInputStream()); - } catch (IOException e) { - log.error("Could not read input from stream", e); - throw new UncheckedIOException(e); - } } - @Operation(responses = { - @ApiResponse(responseCode = "200", - content = @Content( - mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE, - schema = @Schema(type = "string", format = "binary"))) - }) + @ApiResponse(responseCode = "200", + content = @Content(schema = @Schema(type = "string", format = "binary"))) @GetMapping(path = "/{id}/data") - public ResponseEntity downloadArtifact(@PathVariable UUID id) { + public ResponseEntity downloadArtifact(@PathVariable UUID id) throws IOException { ArtifactDto artifactDto = artifactService.getById(id); InputStreamResource data = new InputStreamResource(archiveService.getArtifact(id)); diff --git a/service/fling/src/main/java/net/friedl/fling/controller/CommonExceptionHandler.java b/service/fling/src/main/java/net/friedl/fling/controller/CommonExceptionHandler.java new file mode 100644 index 0000000..f149e3e --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/controller/CommonExceptionHandler.java @@ -0,0 +1,45 @@ +package net.friedl.fling.controller; + +import java.io.IOException; +import java.io.UncheckedIOException; +import javax.persistence.EntityNotFoundException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ControllerAdvice +public class CommonExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler({EntityNotFoundException.class, IOException.class, UncheckedIOException.class}) + public ResponseEntity handleNotFound(Exception ex, WebRequest request) + throws Exception { + + HttpHeaders headers = new HttpHeaders(); + + if (ex instanceof IOException) { + log.error("IO Error", ex); + HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; + return handleExceptionInternal(ex, null, headers, status, request); + } + + if (ex instanceof UncheckedIOException) { + log.error("IO Error", ex); + HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; + return handleExceptionInternal(ex, null, headers, status, request); + } + + if (ex instanceof EntityNotFoundException) { + log.error("Entity not found", ex); + HttpStatus status = HttpStatus.NOT_FOUND; + return handleExceptionInternal(ex, null, headers, status, request); + } + + return super.handleException(ex, request); + } +} diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index 3e9c0bf..bdd5163 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -1,5 +1,6 @@ package net.friedl.fling.controller; +import java.io.IOException; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -70,7 +71,7 @@ public class FlingController { } @DeleteMapping("/{id}") - public void deleteFling(@PathVariable UUID id) { + public void deleteFling(@PathVariable UUID id) throws IOException { flingService.delete(id); } @@ -81,7 +82,7 @@ public class FlingController { schema = @Schema(type = "string", format = "binary"))) }) @GetMapping(path = "/{id}/data") - public ResponseEntity getFlingData(@PathVariable UUID id) { + public ResponseEntity getFlingData(@PathVariable UUID id) throws IOException { FlingDto flingDto = flingService.getById(id); InputStreamResource data = new InputStreamResource(archiveService.getFling(id)); diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java index 7f27957..be6b682 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java @@ -13,8 +13,8 @@ import lombok.NoArgsConstructor; @Data @Builder -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor @Schema(name = "Artifact") public class ArtifactDto { @Schema(accessMode = AccessMode.READ_ONLY, type = "string") diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java index a34e438..8165f09 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/ArtifactRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import net.friedl.fling.persistence.entities.ArtifactEntity; public interface ArtifactRepository extends JpaRepository { - List findAllByFlingId(Long flingId); + List findAllByFlingId(UUID flingId); } diff --git a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java index 4a35965..0eadaa2 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java @@ -1,5 +1,6 @@ package net.friedl.fling.service; +import java.io.IOException; import java.util.UUID; import javax.transaction.Transactional; import javax.validation.constraints.NotNull; @@ -66,20 +67,11 @@ public class ArtifactService { * Deletes an artifact identified by {@code id}. NOOP if the artifact cannot be found. * * @param id An {@link UUID} that identifies the artifact + * @throws IOException If the deletion failed */ - public void delete(UUID id) { - if (id == null) - return; - - ArtifactEntity artifactEntity = artifactRepository.findById(id).orElse(null); - - if (artifactEntity == null) { - log.warn("Cannot delete artifact {}. Artifact not found.", id); - return; - } - + public void delete(UUID id) throws IOException { archiveService.deleteArtifact(id); - artifactRepository.delete(artifactEntity); - log.info("Deleted artifact {}", artifactEntity); + artifactRepository.deleteById(id); + log.info("Deleted artifact {}", id); } } diff --git a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java index 9cce26e..2e074f2 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java @@ -1,5 +1,6 @@ package net.friedl.fling.service; +import java.io.IOException; import java.security.MessageDigest; import java.util.Base64; import java.util.List; @@ -87,7 +88,7 @@ public class FlingService { return flingMapper.map(flingEntity); } - public void delete(UUID id) { + public void delete(UUID id) throws IOException { archiveService.deleteFling(id); flingRepository.deleteById(id); log.debug("Deleted fling {}", id); @@ -95,6 +96,10 @@ public class FlingService { public boolean validateAuthCode(UUID id, String authCode) { FlingEntity flingEntity = flingRepository.getOne(id); + if(StringUtils.hasText(flingEntity.getAuthCode()) != StringUtils.hasText(authCode)) { + return false; // only one of them is empty; implicit null safety check + } + boolean valid = flingEntity.getAuthCode().equals(hashAuthCode(authCode)); log.debug("Provided authentication for {} is {} valid", id, valid ? "" : "not"); return valid; diff --git a/service/fling/src/main/java/net/friedl/fling/service/ServiceException.java b/service/fling/src/main/java/net/friedl/fling/service/ServiceException.java deleted file mode 100644 index 90abbf2..0000000 --- a/service/fling/src/main/java/net/friedl/fling/service/ServiceException.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.friedl.fling.service; - -public class ServiceException extends Exception { - private static final long serialVersionUID = 2159182914434903969L; - - /** - * {@inheritDoc} - */ - public ServiceException() { - super(); - } - - /** - * {@inheritDoc} - */ - public ServiceException(String message) { - super(message); - } - - /** - * {@inheritDoc} - */ - public ServiceException(String message, Throwable cause) { - super(message, cause); - } - - /** - * {@inheritDoc} - */ - public ServiceException(Throwable cause) { - super(cause); - } - - /** - * {@inheritDoc} - */ - protected ServiceException(String message, Throwable cause, - boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java deleted file mode 100644 index fda5fd6..0000000 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveException.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.friedl.fling.service.archive; - -public class ArchiveException extends RuntimeException { - private static final long serialVersionUID = 6216735865308056261L; - - /** - * Constructs a new exception with {@code null} as its detail message. The cause is not - * initialized, and may subsequently be initialized by a call to {@link #initCause}. - */ - public ArchiveException() { - super(); - } - - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and - * may subsequently be initialized by a call to {@link #initCause}. - * - * @param message the detail message. The detail message is saved for later retrieval by the - * {@link #getMessage()} method. - */ - public ArchiveException(String message) { - super(message); - } - - /** - * Constructs a new exception with the specified detail message and cause. - *

- * Note that the detail message associated with {@code cause} is not automatically - * incorporated in this exception's detail message. - * - * @param message the detail message (which is saved for later retrieval by the - * {@link #getMessage()} method). - * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). - * (A {@code null} value is permitted, and indicates that the cause is nonexistent or - * unknown.) - * @since 1.4 - */ - public ArchiveException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new exception with the specified cause and a detail message of - * {@code (cause==null ? null : cause.toString())} (which typically contains the class and detail - * message of {@code cause}). This constructor is useful for exceptions that are little more than - * wrappers for other throwables (for example, - * {@link java.security.PrivilegedActionArchiveException}). - * - * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). - * (A {@code null} value is permitted, and indicates that the cause is nonexistent or - * unknown.) - * @since 1.4 - */ - public ArchiveException(Throwable cause) { - super(cause); - } - - /** - * Constructs a new exception with the specified detail message, cause, suppression enabled or - * disabled, and writable stack trace enabled or disabled. - * - * @param message the detail message. - * @param cause the cause. (A {@code null} value is permitted, and indicates that the cause is - * nonexistent or unknown.) - * @param enableSuppression whether or not suppression is enabled or disabled - * @param writableStackTrace whether or not the stack trace should be writable - * @since 1.7 - */ - protected ArchiveException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java index 922bed9..f1472c4 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java @@ -1,8 +1,8 @@ package net.friedl.fling.service.archive; +import java.io.IOException; import java.io.InputStream; import java.util.UUID; -import java.util.zip.ZipInputStream; /** * Interface for persisting artifacts @@ -16,15 +16,15 @@ public interface ArchiveService { * @param id The artifact id * @return An {@link InputStream} for reading the artifact */ - InputStream getArtifact(UUID artifactId); + InputStream getArtifact(UUID artifactId) throws IOException; /** * Retrieve a packaged fling from the archive * * @param flingId The fling id - * @return An {@link ZipInputStream} representing the fling and its artifacts + * @return An {@link InputStream} representing the fling and its artifacts */ - ZipInputStream getFling(UUID flingId); + InputStream getFling(UUID flingId) throws IOException; /** * Store an artifact @@ -32,19 +32,19 @@ public interface ArchiveService { * @param artifactStream The artifact to store represented as {@link InputStream} * @param artifactId The id of the artifact. Must be an existing artifact in the DB. Not null. */ - void storeArtifact(UUID artifactId, InputStream artifactStream); + void storeArtifact(UUID artifactId, InputStream artifactStream) throws IOException; /** * Delete an artifact * * @param id The unique artifact id */ - void deleteArtifact(UUID artifactId); + void deleteArtifact(UUID artifactId) throws IOException; /** * Delete a fling * * @param flingId The unique fling id */ - void deleteFling(UUID flingId); + void deleteFling(UUID flingId) throws IOException; } diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java index ee72eca..ffbb18e 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java @@ -15,14 +15,12 @@ import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.Map; import java.util.UUID; -import java.util.zip.ZipInputStream; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.transaction.Transactional; import javax.validation.constraints.NotBlank; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Service; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.friedl.fling.persistence.entities.ArtifactEntity; import net.friedl.fling.persistence.repositories.ArtifactRepository; @@ -49,6 +47,7 @@ public class FileSystemArchive implements ArchiveService { public void postConstruct() { try { Files.createDirectories(archivePath); + log.debug("Using archive path {}", archivePath); } catch (IOException e) { log.error("Could not create directory at archive path {}", archivePath); throw new UncheckedIOException(e); @@ -60,6 +59,7 @@ public class FileSystemArchive implements ArchiveService { filesystems.forEach((uri, zfs) -> { try { zfs.close(); + log.debug("Closed {}", uri); } catch (IOException e) { log.error("Could not close file system for {}", uri); } @@ -67,8 +67,7 @@ public class FileSystemArchive implements ArchiveService { } @Override - @SneakyThrows - public InputStream getArtifact(UUID artifactId) { + public InputStream getArtifact(UUID artifactId) throws IOException { log.debug("Reading data for artifact {}", artifactId); FileSystem zipDisk = getZipDisk(artifactId); @@ -79,17 +78,15 @@ public class FileSystemArchive implements ArchiveService { } @Override - @SneakyThrows - public ZipInputStream getFling(UUID flingId) { + public InputStream getFling(UUID flingId) throws IOException { log.debug("Reading data for fling {}", flingId); Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); log.debug("Zip disk path is {}", zipDiskPath); - return new ZipInputStream(new FileInputStream(zipDiskPath.toFile())); + return new FileInputStream(zipDiskPath.toFile()); } @Override - @SneakyThrows - public void storeArtifact(UUID artifactId, InputStream artifactStream) { + public void storeArtifact(UUID artifactId, InputStream artifactStream) throws IOException { log.debug("Storing artifact {}", artifactId); setArchived(artifactId, false); @@ -103,8 +100,7 @@ public class FileSystemArchive implements ArchiveService { } @Override - @SneakyThrows - public void deleteArtifact(UUID artifactId) { + public void deleteArtifact(UUID artifactId) throws IOException { log.debug("Deleting artifact {}", artifactId); FileSystem zipDisk = getZipDisk(artifactId); Files.delete(getZipDiskPath(artifactId, zipDisk)); @@ -115,8 +111,7 @@ public class FileSystemArchive implements ArchiveService { } @Override - @SneakyThrows - public void deleteFling(UUID flingId) { + public void deleteFling(UUID flingId) throws IOException { URI zipDiskUri = resolveFlingUri(flingId); log.debug("Closing zip disk at {}", zipDiskUri); @@ -135,8 +130,9 @@ public class FileSystemArchive implements ArchiveService { Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath); Files.delete(zipDiskPath); + + artifactRepository.findAllByFlingId(flingId).forEach(ar -> ar.setArchived(false)); } - } private void setArchived(UUID artifactId, boolean archived) { @@ -168,7 +164,7 @@ public class FileSystemArchive implements ArchiveService { URI uri = resolveArtifactUri(artifactId); log.debug("Looking for zip disk at uri {}", uri); - // make sure nobody opens closes, deletes or interleavingly opens the filesystem while it is + // make sure nobody closes, deletes or interleavingly opens the filesystem while it is // being opened synchronized (filesystems) { if (!filesystems.containsKey(uri)) { @@ -214,4 +210,9 @@ public class FileSystemArchive implements ArchiveService { public void setArchivePath(String archivePath) { this.archivePath = Paths.get(archivePath); } + + public void setArchivePath(Path archivePath) { + this.archivePath = archivePath; + } + } diff --git a/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java index 32ef8b7..c87264e 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java @@ -1,9 +1,37 @@ package net.friedl.fling.controller; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Instant; +import java.util.UUID; +import javax.persistence.EntityNotFoundException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.FilterType; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import net.friedl.fling.model.dto.ArtifactDto; +import net.friedl.fling.service.ArtifactService; +import net.friedl.fling.service.archive.ArchiveService; @WebMvcTest(controllers = ArtifactController.class, // do auto-configure security @@ -11,34 +39,102 @@ import org.springframework.context.annotation.FilterType; // do not try to create beans in security excludeFilters = @Filter(type = FilterType.REGEX, pattern = "net.friedl.fling.security.*")) class ArtifactControllerTest { -// @Autowired -// private MockMvc mvc; -// -// @MockBean -// private ArtifactService artifactService; -// -// @Test -// public void testGetArtifacts_noArtifacts_empty() throws Exception { -//// Long flingId = 123L; -//// -//// when(artifactService.findAllArtifacts(flingId)).thenReturn(List.of()); -//// -//// mvc.perform(get("/api/artifacts").param("flingId", flingId.toString())) -//// .andExpect(jsonPath("$", hasSize(0))); -// } -// -// @Test -// public void testGetArtifacts_hasArtifacts_allArtifacts() throws Exception { -//// Long flingId = 123L; -//// String artifactName = "TEST"; -//// -//// ArtifactDto artifactDto = new ArtifactDto(); -//// artifactDto.setName(artifactName); -//// -//// when(artifactService.findAllArtifacts(flingId)).thenReturn(List.of(artifactDto)); -//// -//// mvc.perform(get("/api/artifacts").param("flingId", flingId.toString())) -//// .andExpect(jsonPath("$", hasSize(1))) -//// .andExpect(jsonPath("$[0].name", equalTo(artifactName))); -// } + @Autowired + private MockMvc mvc; + + @MockBean + private ArtifactService artifactService; + + @MockBean + private ArchiveService archiveService; + + private static final UUID ARTIFACT_ID = UUID.randomUUID(); + + private ArtifactDto artifactDto = + new ArtifactDto(ARTIFACT_ID, Path.of("testArtifact"), Instant.EPOCH, false); + + @Test + public void getArtifact_noArtifactWithId_notFound() throws Exception { + when(artifactService.getById(ARTIFACT_ID)).thenThrow(EntityNotFoundException.class); + + mvc.perform(get("/api/artifacts/{id}", ARTIFACT_ID)) + .andExpect(status().isNotFound()); + } + + @Test + public void getArtifacts_ok() throws Exception { + when(artifactService.getById(ARTIFACT_ID)).thenReturn(artifactDto); + + mvc.perform(get("/api/artifacts/{id}", ARTIFACT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo(ARTIFACT_ID.toString()))); + } + + @Test + public void deleteArtifact_noArtifactWithId_notFound() throws Exception { + doThrow(EntityNotFoundException.class).when(artifactService).delete(ARTIFACT_ID); + + mvc.perform(delete("/api/artifacts/{id}", ARTIFACT_ID)) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteArtifact_ok() throws Exception { + mvc.perform(delete("/api/artifacts/{id}", ARTIFACT_ID)) + .andExpect(status().isOk()); + } + + @Test + public void uploadArtifact_ioError_serverError() throws Exception { + doThrow(IOException.class).when(archiveService).storeArtifact(any(), any()); + + byte[] payload = "Payload".getBytes(); + mvc.perform(post("/api/artifacts/{id}/data", ARTIFACT_ID) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .content(payload)) + .andExpect(status().isInternalServerError()); + } + + @Test + public void uploadArtifact_ok() throws Exception { + byte[] payload = "Payload".getBytes(); + mvc.perform(post("/api/artifacts/{id}/data", ARTIFACT_ID) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .content(payload)) + .andExpect(status().isOk()); + } + + @Test + public void downloadArtifact_noArtifact_notFound() throws Exception { + doThrow(EntityNotFoundException.class).when(artifactService).getById(ARTIFACT_ID); + + mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID)) + .andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION)) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) + .andExpect(status().isNotFound()); + } + + @Test + public void downloadArtifact_ioError_serverError() throws Exception { + doThrow(IOException.class).when(archiveService).getArtifact(ARTIFACT_ID); + + mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID)) + .andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION)) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) + .andExpect(status().isInternalServerError()); + } + + @Test + public void downloadArtifact_ok() throws Exception { + when(artifactService.getById(ARTIFACT_ID)).thenReturn(artifactDto); + byte[] testData = "test".getBytes(); + when(archiveService.getArtifact(any())).thenReturn(new ByteArrayInputStream(testData)); + + mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID)) + .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) + .andExpect(header().exists(HttpHeaders.CONTENT_DISPOSITION)) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, + Matchers.containsString("attachment;filename"))) + .andExpect(content().bytes(testData)); + } } diff --git a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java new file mode 100644 index 0000000..3d0e80c --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java @@ -0,0 +1,206 @@ +package net.friedl.fling.controller; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import javax.persistence.EntityNotFoundException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.FilterType; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.friedl.fling.model.dto.ArtifactDto; +import net.friedl.fling.model.dto.FlingDto; +import net.friedl.fling.service.ArtifactService; +import net.friedl.fling.service.FlingService; +import net.friedl.fling.service.archive.ArchiveService; + +@WebMvcTest(controllers = FlingController.class, + // do auto-configure security + excludeAutoConfiguration = SecurityAutoConfiguration.class, + // do not try to create beans in security + excludeFilters = @Filter(type = FilterType.REGEX, pattern = "net.friedl.fling.security.*")) +public class FlingControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper mapper; + + @MockBean + private FlingService flingService; + + @MockBean + private ArtifactService artifactService; + + @MockBean + private ArchiveService archiveService; + + private static final UUID flingId = UUID.randomUUID(); + + private FlingDto flingDto = new FlingDto(flingId, "name", Instant.EPOCH, "shareId", "authCode", + false, true, true, 1, null); + + private ArtifactDto artifactDto = + new ArtifactDto(UUID.randomUUID(), Path.of("testArtifact"), Instant.EPOCH, false); + + @Test + public void getFlings_noFlings_empty() throws Exception { + when(flingService.findAll()).thenReturn(List.of()); + + mockMvc.perform(get("/api/fling")) + .andExpect(jsonPath("$", hasSize(0))) + .andExpect(status().isOk()); + } + + @Test + public void getFlings_allFlings() throws Exception { + when(flingService.findAll()).thenReturn(List.of(flingDto, flingDto)); + + mockMvc.perform(get("/api/fling")) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].id", equalTo(flingId.toString()))) + .andExpect(status().isOk()); + } + + @Test + public void postFling_ok() throws Exception { + mockMvc.perform(post("/api/fling") + .content(mapper.writeValueAsString(flingDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + public void postArtifact_ok() throws Exception { + mockMvc.perform(post("/api/fling/{id}/artifact", flingId) + .content(mapper.writeValueAsString(artifactDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + public void getFling_noFlingWithId_notFound() throws Exception { + doThrow(EntityNotFoundException.class).when(flingService).getById(flingId); + + mockMvc.perform(get("/api/fling/{id}", flingId)) + .andExpect(status().isNotFound()); + } + + @Test + public void getFling_flingFound_returnsFling() throws Exception { + when(flingService.getById(flingId)).thenReturn(flingDto); + + mockMvc.perform(get("/api/fling/{id}", flingId)) + .andExpect(jsonPath("$.id", equalTo(flingId.toString()))) + .andExpect(status().isOk()); + } + + @Test + public void getFlingByShareId_noFlingWithShareId_notFound() throws Exception { + doThrow(EntityNotFoundException.class).when(flingService).getByShareId("doesNotExist"); + + mockMvc.perform(get("/api/fling/share/{shareId}", "doesNotExist")) + .andExpect(status().isNotFound()); + } + + @Test + public void getFlingByShareId_flingFind_returnsFling() throws Exception { + doReturn(flingDto).when(flingService).getByShareId("shareId"); + + mockMvc.perform(get("/api/fling/share/{shareId}", "shareId")) + .andExpect(jsonPath("$.id", equalTo(flingId.toString()))) + .andExpect(status().isOk()); + } + + @Test + public void deleteFling_noFlingWithId_notFound() throws Exception { + doThrow(EntityNotFoundException.class).when(flingService).delete(flingId); + + mockMvc.perform(delete("/api/fling/{id}", flingId)) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteFling_ok() throws Exception { + doNothing().when(flingService).delete(flingId); + + mockMvc.perform(delete("/api/fling/{id}", flingId)) + .andExpect(status().isOk()); + } + + @Test + public void getFlingData_ioError_serverError() throws Exception { + doThrow(IOException.class).when(archiveService).getFling(flingId); + + mockMvc.perform(get("/api/fling/{id}/data", flingId)) + .andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION)) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, + not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) + .andExpect(status().isInternalServerError()); + } + + @Test + public void getFlingData_ok() throws Exception { + when(flingService.getById(flingId)).thenReturn(flingDto); + int[] testZipInt = new int[] { + 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x77, 0xe4, 0x50, 0xc6, + 0x35, + 0xb9, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x74, + 0x65, + 0x73, 0x74, 0x55, 0x54, 0x09, 0x00, 0x03, 0x40, 0x7d, 0x00, 0x5f, 0x37, 0x7d, 0x00, 0x5f, + 0x75, + 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x74, + 0x65, + 0x73, 0x74, 0x0a, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x76, + 0x77, 0xe4, 0x50, 0xc6, 0x35, 0xb9, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x04, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb4, 0x81, 0x00, 0x00, + 0x00, + 0x00, 0x74, 0x65, 0x73, 0x74, 0x55, 0x54, 0x05, 0x00, 0x03, 0x40, 0x7d, 0x00, 0x5f, 0x75, + 0x78, + 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x50, 0x4b, + 0x05, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x43, 0x00, + 0x00, + 0x00, 0x00, 0x00 + }; + byte[] testZip = new byte[testZipInt.length]; + for (int idx = 0; idx < testZip.length; idx++) testZip[idx] = (byte) testZipInt[idx]; + + when(archiveService.getFling(any())).thenReturn(new ByteArrayInputStream(testZip)); + + mockMvc.perform(get("/api/fling/{id}/data", flingId)) + .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) + .andExpect(header().exists(HttpHeaders.CONTENT_DISPOSITION)) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, + Matchers.containsString("attachment;filename"))) + .andExpect(content().bytes(testZip)); + } +} diff --git a/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java new file mode 100644 index 0000000..95c49de --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java @@ -0,0 +1,137 @@ +package net.friedl.fling.service; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.model.dto.ArtifactDto; +import net.friedl.fling.model.mapper.ArtifactMapper; +import net.friedl.fling.model.mapper.ArtifactMapperImpl; +import net.friedl.fling.persistence.entities.ArtifactEntity; +import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.repositories.ArtifactRepository; +import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.service.archive.ArchiveService; + +@ExtendWith(SpringExtension.class) +public class ArtifactServiceTest { + + @Autowired + private ArtifactService artifactService; + + @MockBean + private FlingRepository flingRepository; + + @MockBean + private ArtifactRepository artifactRepository; + + @MockBean + private ArchiveService archiveService; + + private FlingEntity flingEntity; + + private ArtifactEntity artifactEntity1; + + private ArtifactEntity artifactEntity2; + + @TestConfiguration + static class FlingServiceTestConfiguration { + @Bean + public ArtifactMapper artifactMapper() { + return new ArtifactMapperImpl(); + } + + @Bean + public ArtifactService artifactService(ArtifactRepository artifactRepository, + FlingRepository flingRepository, ArtifactMapper artifactMapper, + ArchiveService archiveService) { + + return new ArtifactService(artifactRepository, flingRepository, artifactMapper, archiveService); + } + } + + @BeforeEach + public void beforeEach() { + this.artifactEntity1 = new ArtifactEntity(); + artifactEntity1.setId(UUID.randomUUID()); + artifactEntity1.setUploadTime(Instant.EPOCH); + artifactEntity1.setPath(Path.of("artifact1")); + + this.artifactEntity2 = new ArtifactEntity(); + artifactEntity2.setId(UUID.randomUUID()); + artifactEntity2.setUploadTime(Instant.EPOCH.plus(12000, ChronoUnit.DAYS)); + artifactEntity2.setPath(Path.of("/","/sub","artifact2")); + + this.flingEntity = new FlingEntity(); + flingEntity.setId(UUID.randomUUID()); + flingEntity.setName("fling"); + flingEntity.setCreationTime(Instant.now()); + + when(flingRepository.save(any())).then(new Answer() { + @Override + public FlingEntity answer(InvocationOnMock invocation) throws Throwable { + FlingEntity flingEntity = invocation.getArgument(0); + if(flingEntity.getId() == null) flingEntity.setId(UUID.randomUUID()); + return flingEntity; + } + }); + + when(artifactRepository.save(any())).then(new Answer() { + @Override + public ArtifactEntity answer(InvocationOnMock invocation) throws Throwable { + ArtifactEntity artifactEntity = invocation.getArgument(0); + artifactEntity.setId(UUID.randomUUID()); + return artifactEntity; + } + }); + + } + + @Test + public void getById_artifactExists_ok() { + when(artifactRepository.getOne(artifactEntity1.getId())).thenReturn(artifactEntity1); + + ArtifactDto artifactDto = artifactService.getById(artifactEntity1.getId()); + assertThat(artifactDto.getId(), equalTo(artifactEntity1.getId())); + assertThat(artifactDto.getPath(), equalTo(artifactEntity1.getPath())); + assertThat(artifactDto.getUploadTime(), equalTo(artifactEntity1.getUploadTime())); + } + + @Test + public void create_createsArtifact_ok() { + ArtifactDto artifactToCreate = ArtifactDto.builder() + .uploadTime(Instant.now()) + .path(Path.of("new", "artifacts")) + .build(); + + ArtifactDto createdArtifact = artifactService.create(flingEntity.getId(), artifactToCreate); + + assertThat(createdArtifact.getUploadTime(), equalTo(artifactToCreate.getUploadTime())); + assertThat(createdArtifact.getPath(), equalTo(artifactToCreate.getPath())); + } + + @Test + public void delete_deletesArchiveAndArtifactEntry() throws IOException { + artifactService.delete(artifactEntity1.getId()); + + verify(archiveService).deleteArtifact(artifactEntity1.getId()); + verify(artifactRepository).deleteById(artifactEntity1.getId()); + } + +} diff --git a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java new file mode 100644 index 0000000..df1d1a7 --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java @@ -0,0 +1,181 @@ +package net.friedl.fling.service; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.model.dto.FlingDto; +import net.friedl.fling.model.mapper.FlingMapper; +import net.friedl.fling.model.mapper.FlingMapperImpl; +import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.service.archive.ArchiveService; + +@ExtendWith(SpringExtension.class) +public class FlingServiceTest { + @Autowired + private FlingService flingService; + + @Autowired + private FlingMapper flingMapper; + + @Autowired + private MessageDigest keyHashDigest; + + @MockBean + private FlingRepository flingRepository; + + @MockBean + private ArchiveService archiveService; + + private FlingEntity flingEntity1; + + private FlingEntity flingEntity2; + + @TestConfiguration + static class FlingServiceTestConfiguration { + @Bean + public FlingMapper flingMapper() { + return new FlingMapperImpl(); + } + + @Bean + public MessageDigest keyHashDigest() throws NoSuchAlgorithmException { + return MessageDigest.getInstance("SHA-512"); + } + + @Bean + public FlingService flingService(FlingRepository flingRepository, FlingMapper flingMapper, + ArchiveService archiveService, MessageDigest keyHashDigest) { + return new FlingService(flingRepository, flingMapper, archiveService, keyHashDigest); + } + } + + @BeforeEach + public void beforeEach() { + this.flingEntity1 = new FlingEntity(); + flingEntity1.setId(UUID.randomUUID()); + flingEntity1.setName("fling1"); + flingEntity1.setAuthCode(new String(Hex.encode(keyHashDigest.digest("authCode1".getBytes())))); + flingEntity1.setCreationTime(Instant.now()); + + this.flingEntity2 = new FlingEntity(); + flingEntity2.setId(UUID.randomUUID()); + flingEntity2.setName("fling2"); + flingEntity2.setShareId("shareId2"); + flingEntity2.setCreationTime(Instant.now()); + + when(flingRepository.save(any())).then(new Answer() { + @Override + public FlingEntity answer(InvocationOnMock invocation) throws Throwable { + FlingEntity flingEntity = invocation.getArgument(0); + flingEntity.setId(UUID.randomUUID()); + return flingEntity; + }}); + } + + @Test + public void findAll_noFlings_empty() { + when(flingRepository.findAll()).thenReturn(List.of()); + + assertThat(flingService.findAll(), is(empty())); + } + + @Test + public void findAll_hasFlings_allFlings() { + when(flingRepository.findAll()).thenReturn(List.of(flingEntity1, flingEntity2)); + + assertThat(flingService.findAll(), hasItems( + flingMapper.map(flingEntity1), flingMapper.map(flingEntity2))); + } + + @Test + public void getById_flingDto() { + when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1); + assertThat(flingService.getById(flingEntity1.getId()), equalTo(flingMapper.map(flingEntity1))); + } + + @Test + public void create_emptyFling_defaultValues() { + FlingDto flingDto = new FlingDto(); + + FlingDto createdFling = flingService.create(flingDto); + assertThat(createdFling.getShareId(), not(emptyOrNullString())); + assertThat(createdFling.getAuthCode(), emptyOrNullString()); + } + + @Test + public void create_hasAuthCode_setAuthCode() { + FlingDto flingDto = new FlingDto(); + flingDto.setAuthCode("test"); + + String hashedAuthCode = new String(Hex.encode(keyHashDigest.digest(flingDto.getAuthCode().getBytes()))); + + FlingDto createdFling = flingService.create(flingDto); + assertThat(createdFling.getAuthCode(), is(hashedAuthCode)); + } + + @Test + public void create_hasShareId_setShareId() { + FlingDto flingDto = new FlingDto(); + flingDto.setShareId("test"); + + FlingDto createdFling = flingService.create(flingDto); + assertThat(createdFling.getShareId(), is("test")); + } + + @Test + public void getByShareId_flingDto() { + when(flingRepository.findByShareId("shareId2")).thenReturn(flingEntity2); + + FlingDto foundFling = flingService.getByShareId("shareId2"); + assertThat(foundFling.getShareId(), equalTo("shareId2")); + } + + @Test + public void delete_deletesFromArchiveAndDb() throws IOException { + UUID testId = UUID.randomUUID(); + flingService.delete(testId); + + verify(archiveService).deleteFling(testId); + verify(flingRepository).deleteById(testId); + } + + @Test + public void validateAuthCode_codesMatch_true() { + when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1); + + assertThat(flingService.validateAuthCode(flingEntity1.getId(), "authCode1"), is(true)); + } + + @Test + public void validateAuthCode_codesDoNotMatch_false() { + when(flingRepository.getOne(flingEntity2.getId())).thenReturn(flingEntity2); + + assertThat(flingService.validateAuthCode(flingEntity2.getId(), "authCode1"), is(false)); + } + +} diff --git a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java new file mode 100644 index 0000000..fc34e53 --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java @@ -0,0 +1,300 @@ +package net.friedl.fling.service.archive; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.persistence.entities.ArtifactEntity; +import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.repositories.ArtifactRepository; +import net.friedl.fling.service.archive.impl.FileSystemArchive; + +@ExtendWith(SpringExtension.class) +public class FileSystemArchiveTest { + @Autowired + private FileSystemArchive fileSystemArchive; + + @MockBean + private ArtifactRepository artifactRepository; + + private FlingEntity flingEntity1; + + private FlingEntity flingEntity2; + + private ArtifactEntity artifactEntity1; + + private ArtifactEntity artifactEntity2; + + @TempDir + static Path tempDir; + + @TestConfiguration + static class FlingServiceTestConfiguration { + @Bean + public FileSystemArchive fileSystemArchive(ArtifactRepository artifactRepository) + throws URISyntaxException { + FileSystemArchive fileSystemArchive = new FileSystemArchive(artifactRepository); + fileSystemArchive.setArchivePath(tempDir); + return fileSystemArchive; + } + } + + @BeforeEach + public void beforeEach() throws IOException, URISyntaxException { + repopulateArchivePath(); + setupTestEntites(); + } + + @Test + public void getArtifact_flingDiskForFlingIdDoesNotExist_throws() { + flingEntity1.setId(UUID.randomUUID()); + artifactEntity1.setFling(flingEntity1); + when(artifactRepository.getOne(artifactEntity1.getId())).thenReturn(artifactEntity1); + + assertThrows(IOException.class, () -> fileSystemArchive.getArtifact(artifactEntity1.getId())); + } + + @Test + public void getArtifact_returnsArtifact() throws IOException { + when(artifactRepository.getOne(artifactEntity1.getId())).thenReturn(artifactEntity1); + + InputStream expectedArtifact = + getClass().getClassLoader().getResourceAsStream("filesystem/artifacts/artifact1"); + byte[] expectedArtifactData = expectedArtifact.readAllBytes(); + expectedArtifact.close(); + + InputStream retrievedArtifact = fileSystemArchive.getArtifact(artifactEntity1.getId()); + byte[] retrievedArtifactData = retrievedArtifact.readAllBytes(); + retrievedArtifact.close(); + + assertThat(retrievedArtifactData, equalTo(expectedArtifactData)); + } + + @Test + public void getFling_doesNotExist_throws() { + assertThrows(IOException.class, () -> fileSystemArchive.getFling(UUID.randomUUID())); + } + + @Test + public void getFling_returnsFling() throws IOException { + UUID flingUUID = new UUID(0, 0); + InputStream expectedFling = getClass().getClassLoader() + .getResourceAsStream("filesystem/archive_path/" + flingUUID.toString() + ".zip"); + byte[] expectedFlingData = expectedFling.readAllBytes(); + expectedFling.close(); + + InputStream retrievedFling = fileSystemArchive.getFling(flingUUID); + byte[] retrievedFlingData = retrievedFling.readAllBytes(); + retrievedFling.close(); + + assertThat(retrievedFlingData, equalTo(expectedFlingData)); + } + + @Test + public void deleteArtifact_setsArchivedFalse() throws IOException { + when(artifactRepository.getOne(artifactEntity1.getId())).thenReturn(artifactEntity1); + + fileSystemArchive.deleteArtifact(artifactEntity1.getId()); + + assertThat(artifactEntity1.getArchived(), equalTo(false)); + + InputStream flingStream = + new FileInputStream( + tempDir.resolve(artifactEntity1.getFling().getId().toString() + ".zip").toFile()); + ZipInputStream zis = new ZipInputStream(flingStream); + ZipEntry zipEntry; + while ((zipEntry = zis.getNextEntry()) != null) { + assertThat(zipEntry.getName(), not(equalTo(artifactEntity1.getPath().toString()))); + zis.closeEntry(); + } + zis.close(); + } + + @Test + public void deleteArtifact_deletesArtifactFromZipDisk() throws IOException { + when(artifactRepository.getOne(artifactEntity1.getId())).thenReturn(artifactEntity1); + + fileSystemArchive.deleteArtifact(artifactEntity1.getId()); + + InputStream flingStream = + new FileInputStream( + tempDir.resolve(artifactEntity1.getFling().getId().toString() + ".zip").toFile()); + ZipInputStream zis = new ZipInputStream(flingStream); + ZipEntry zipEntry; + while ((zipEntry = zis.getNextEntry()) != null) { + assertThat(zipEntry.getName(), not(equalTo(artifactEntity1.getPath().toString()))); + zis.closeEntry(); + } + zis.close(); + } + + @Test + public void storeArtifact_setsArchivedTrue() throws IOException, URISyntaxException { + InputStream artifact2Stream = new FileInputStream( + new File(getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI())); + when(artifactRepository.getOne(artifactEntity2.getId())).thenReturn(artifactEntity2); + + fileSystemArchive.storeArtifact(artifactEntity2.getId(), artifact2Stream); + + artifact2Stream.close(); + + assertThat(artifactEntity2.getArchived(), equalTo(true)); + } + + @Test + public void storeArtifact_storesArtifactToFlingDisk() throws URISyntaxException, IOException { + InputStream artifact2Stream = new FileInputStream( + new File(getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI())); + when(artifactRepository.getOne(artifactEntity2.getId())).thenReturn(artifactEntity2); + + fileSystemArchive.storeArtifact(artifactEntity2.getId(), artifact2Stream); + + artifact2Stream.close(); + + InputStream flingStream = + new FileInputStream( + tempDir.resolve(artifactEntity2.getFling().getId().toString() + ".zip").toFile()); + ZipInputStream zis = new ZipInputStream(flingStream); + ZipEntry zipEntry; + List diskEntries = new LinkedList<>(); + while ((zipEntry = zis.getNextEntry()) != null) { + diskEntries.add(zipEntry.getName()); + zis.closeEntry(); + } + zis.close(); + + assertThat(diskEntries, hasItem(Path.of("/").relativize(artifactEntity2.getPath()).toString())); + } + + @Test + public void deleteFling_setsArchivedFalseForAllContainedArtifacts() throws IOException { + when(artifactRepository.findAllByFlingId(artifactEntity1.getFling().getId())) + .thenReturn(List.of(artifactEntity1)); + + fileSystemArchive.deleteFling(artifactEntity1.getFling().getId()); + + assertThat(artifactEntity1.getArchived(), equalTo(false)); + } + + @Test + public void deleteFling_deletesZipDisk() throws IOException { + assertThat(Files.exists(tempDir.resolve(artifactEntity1.getFling().getId() + ".zip")), + equalTo(true)); + + fileSystemArchive.deleteFling(artifactEntity1.getFling().getId()); + + assertThat(Files.exists(tempDir.resolve(artifactEntity1.getFling().getId() + ".zip")), + equalTo(false)); + } + + private void repopulateArchivePath() throws IOException, URISyntaxException { + Files.walkFileTree(tempDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) + throws IOException { + if (e == null) { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } else { + // directory iteration failed + throw e; + } + } + }); + + Path source = + Path.of(getClass().getClassLoader().getResource("filesystem/archive_path").toURI()); + Path target = tempDir; + Files.walkFileTree(source, Set.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, + new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + Path targetdir = target.resolve(source.relativize(dir)); + try { + Files.copy(dir, targetdir); + } catch (FileAlreadyExistsException e) { + if (!Files.isDirectory(targetdir)) + throw e; + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.copy(file, target.resolve(source.relativize(file))); + return FileVisitResult.CONTINUE; + } + }); + } + + private void setupTestEntites() { + // Fling1/Artifact1 + this.artifactEntity1 = new ArtifactEntity(); + artifactEntity1.setId(UUID.randomUUID()); + artifactEntity1.setUploadTime(Instant.EPOCH); + artifactEntity1.setPath(Path.of("artifact1")); + artifactEntity1.setArchived(true); + + this.flingEntity1 = new FlingEntity(); + flingEntity1.setId(new UUID(0, 0)); + flingEntity1.setName("fling1"); + flingEntity1.setCreationTime(Instant.now()); + + artifactEntity1.setFling(flingEntity1); + + + // Fling2/Artifact2 + this.artifactEntity2 = new ArtifactEntity(); + artifactEntity2.setId(UUID.randomUUID()); + artifactEntity2.setUploadTime(Instant.EPOCH.plus(12000, ChronoUnit.DAYS)); + artifactEntity2.setPath(Path.of("/", "/sub", "artifact2")); + artifactEntity2.setArchived(false); + + this.flingEntity2 = new FlingEntity(); + flingEntity2.setId(new UUID(1, 0)); + flingEntity2.setName("fling2"); + flingEntity2.setCreationTime(Instant.EPOCH); + + artifactEntity2.setFling(flingEntity2); + } +} diff --git a/service/fling/src/test/resources/filesystem/archive_path/.keep b/service/fling/src/test/resources/filesystem/archive_path/.keep new file mode 100644 index 0000000..e69de29 diff --git a/service/fling/src/test/resources/filesystem/artifacts/artifact1 b/service/fling/src/test/resources/filesystem/artifacts/artifact1 new file mode 100644 index 0000000..5105e15 --- /dev/null +++ b/service/fling/src/test/resources/filesystem/artifacts/artifact1 @@ -0,0 +1 @@ +artifact1 ok diff --git a/service/fling/src/test/resources/filesystem/artifacts/artifact2 b/service/fling/src/test/resources/filesystem/artifacts/artifact2 new file mode 100644 index 0000000..59dbf5e --- /dev/null +++ b/service/fling/src/test/resources/filesystem/artifacts/artifact2 @@ -0,0 +1 @@ +artifact2 ok From 00becacd475629f08058949411a479ce3c154144 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 5 Jul 2020 20:16:59 +0200 Subject: [PATCH 05/30] Add test zips --- .gitignore | 4 ++-- service/fling/.attach_pid38548 | 0 .../00000000-0000-0000-0000-000000000000.zip | Bin 0 -> 181 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 service/fling/.attach_pid38548 create mode 100644 service/fling/src/test/resources/filesystem/archive_path/00000000-0000-0000-0000-000000000000.zip diff --git a/.gitignore b/.gitignore index afcc897..678aa0f 100644 --- a/.gitignore +++ b/.gitignore @@ -243,7 +243,6 @@ modules.xml *.war *.nar *.ear -*.zip *.tar.gz *.rar @@ -321,6 +320,7 @@ pids *.pid *.seed *.pid.lock +.attach_pid* # Directory for instrumented libs generated by jscoverage/JSCover lib-cov @@ -500,4 +500,4 @@ gradle-app.setting ### Gradle Patch ### **/build/ -# End of https://www.gitignore.io/api/git,vim,web,node,java,react,linux,macos,emacs,maven,gradle,windows,eclipse,intellij+all,visualstudiocode \ No newline at end of file +# End of https://www.gitignore.io/api/git,vim,web,node,java,react,linux,macos,emacs,maven,gradle,windows,eclipse,intellij+all,visualstudiocode diff --git a/service/fling/.attach_pid38548 b/service/fling/.attach_pid38548 deleted file mode 100644 index e69de29..0000000 diff --git a/service/fling/src/test/resources/filesystem/archive_path/00000000-0000-0000-0000-000000000000.zip b/service/fling/src/test/resources/filesystem/archive_path/00000000-0000-0000-0000-000000000000.zip new file mode 100644 index 0000000000000000000000000000000000000000..7d374290e85150dd13363d08539a95d816203515 GIT binary patch literal 181 zcmWIWW@h1H00E2irvX-fW|Z>+*&xixAj6PYRFavNm|S8Q8p6rIth0$R-WZ5WE4UdL zSza(RFn|fTB8B{Ht^jXFCOKwYCQ5)TVqgT~EsY=+Tx)0uE5v*>vjezfzTgF I2Y@&X0KwBD^#A|> literal 0 HcmV?d00001 From 415687c60153350ed6f6ab680582faf56754dde4 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Fri, 10 Jul 2020 06:55:39 +0200 Subject: [PATCH 06/30] Authorization and Authentication refactoring and tests --- .../net/friedl/fling/FlingConfiguration.java | 9 +- .../FlingSecurityConfiguration.java | 23 +-- .../fling/controller/ArtifactController.java | 7 +- .../controller/AuthenticationController.java | 53 ++++++ .../fling/controller/FlingController.java | 8 +- .../controller/OpenApiConfiguration.java | 2 +- .../friedl/fling/model/dto/AdminAuthDto.java | 21 +++ .../net/friedl/fling/model/dto/FlingDto.java | 1 - .../friedl/fling/model/dto/UserAuthDto.java | 21 +++ .../fling/model/json/PathDeserializer.java | 2 +- .../fling/model/json/PathSerializer.java | 3 +- .../fling/model/mapper/ArtifactMapper.java | 2 + .../fling/model/mapper/FlingMapper.java | 2 + .../persistence/entities/ArtifactEntity.java | 5 +- .../persistence/entities/FlingEntity.java | 3 +- .../fling/security/AuthorizationService.java | 71 -------- .../fling/security/FlingAuthorities.java | 32 ++++ .../friedl/fling/security/FlingAuthority.java | 5 - .../security/FlingWebSecurityConfigurer.java | 14 +- .../AuthenticationController.java | 33 ---- .../authentication/AuthenticationService.java | 101 ----------- .../authentication/FlingAdminAuthority.java | 15 ++ .../security/authentication/FlingToken.java | 25 ++- .../authentication/FlingUserAuthority.java | 25 +++ .../authentication/GrantedFlingAuthority.java | 33 ---- .../JwtAuthenticationFilter.java | 15 +- .../authentication/dto/OwnerAuthDto.java | 11 -- .../authentication/dto/UserAuthDto.java | 11 -- .../friedl/fling/service/ArtifactService.java | 4 +- .../fling/service/AuthenticationService.java | 116 +++++++++++++ .../fling/service/AuthorizationService.java | 68 ++++++++ .../friedl/fling/service/FlingService.java | 14 +- .../fling/service/archive/ArchiveService.java | 6 +- .../archive/impl/FileSystemArchive.java | 4 +- .../spring-configuration-metadata.json | 2 +- .../src/main/resources/application-local.yml | 8 +- .../src/main/resources/application-prod.yml | 16 +- .../controller/ArtifactControllerTest.java | 6 +- .../AuthenticationControllerTest.java | 87 ++++++++++ .../fling/controller/FlingControllerTest.java | 20 +++ .../net/friedl/fling/model/FlingDtoTest.java | 8 +- .../fling/service/ArtifactServiceTest.java | 25 +-- .../service/AuthenticationServiceTest.java | 159 ++++++++++++++++++ .../service/AuthorizationServiceTest.java | 116 +++++++++++++ .../fling/service/FlingServiceTest.java | 51 +++--- .../archive/FileSystemArchiveTest.java | 8 +- .../resources/application-test.properties | 3 + 47 files changed, 886 insertions(+), 388 deletions(-) rename service/fling/src/main/java/net/friedl/fling/{security => }/FlingSecurityConfiguration.java (72%) create mode 100644 service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java create mode 100644 service/fling/src/main/java/net/friedl/fling/model/dto/AdminAuthDto.java create mode 100644 service/fling/src/main/java/net/friedl/fling/model/dto/UserAuthDto.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java create mode 100644 service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/FlingAuthority.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java create mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java create mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java delete mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java create mode 100644 service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java create mode 100644 service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java create mode 100644 service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java create mode 100644 service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java create mode 100644 service/fling/src/test/resources/application-test.properties diff --git a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java index 7f97420..979d530 100644 --- a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java @@ -1,10 +1,10 @@ package net.friedl.fling; import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -16,9 +16,10 @@ import net.friedl.fling.model.json.PathSerializer; @Configuration public class FlingConfiguration { + @Bean - public MessageDigest keyHashDigest() throws NoSuchAlgorithmException { - return MessageDigest.getInstance("SHA-512"); + public PasswordEncoder passwordEncoder() { + return new Argon2PasswordEncoder(); } @Bean diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java b/service/fling/src/main/java/net/friedl/fling/FlingSecurityConfiguration.java similarity index 72% rename from service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java rename to service/fling/src/main/java/net/friedl/fling/FlingSecurityConfiguration.java index ac75054..43b437e 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingSecurityConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/FlingSecurityConfiguration.java @@ -1,8 +1,7 @@ -package net.friedl.fling.security; +package net.friedl.fling; import java.nio.charset.StandardCharsets; import java.security.Key; -import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,26 +14,18 @@ import lombok.Data; @Configuration @ConfigurationProperties("fling.security") public class FlingSecurityConfiguration { - private List allowedOrigins; - - private String adminUser; - - private String adminPassword; - private String signingKey; - private Long jwtExpiration; + @Bean + public JwtParser jwtParser() { + return Jwts.parserBuilder() + .setSigningKey(jwtSigningKey()) + .build(); + } @Bean public Key jwtSigningKey() { byte[] key = signingKey.getBytes(StandardCharsets.UTF_8); return Keys.hmacShaKeyFor(key); } - - @Bean - public JwtParser jwtParser(Key jwtSignigKey) { - return Jwts.parserBuilder() - .setSigningKey(jwtSignigKey) - .build(); - } } diff --git a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java index 15f1c4d..7dcde8e 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java @@ -9,6 +9,7 @@ import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -27,6 +28,7 @@ import net.friedl.fling.service.archive.ArchiveService; @RestController @RequestMapping("/api/artifacts") @Tag(name = "artifact", description = "Operations on /api/artifacts") +@Validated public class ArtifactController { private ArtifactService artifactService; @@ -52,8 +54,9 @@ public class ArtifactController { @RequestBody(content = @Content(schema = @Schema(type = "string", format = "binary"))) @PostMapping(path = "/{id}/data") - public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) throws IOException { - archiveService.storeArtifact(id, request.getInputStream()); + public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) + throws IOException { + archiveService.storeArtifact(id, request.getInputStream()); } @ApiResponse(responseCode = "200", diff --git a/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java b/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java new file mode 100644 index 0000000..ec896fe --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java @@ -0,0 +1,53 @@ +package net.friedl.fling.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import net.friedl.fling.model.dto.AdminAuthDto; +import net.friedl.fling.model.dto.UserAuthDto; +import net.friedl.fling.service.AuthenticationService; + +@RestController +@RequestMapping("/api/auth") +@Tag(name = "auth", description = "Operations on /api/auth") +@Validated +public class AuthenticationController { + + private AuthenticationService authenticationService; + + @Autowired + public AuthenticationController(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + @Operation(description = "Authenticates the fling admin by username and password") + @ApiResponse(responseCode = "200", + description = "JWT Token authenticating the admin of this fling instance") + @ApiResponse(responseCode = "403", + description = "Authentication failed, username or password are wrong") + @PostMapping(path = "/admin") + public String authenticateOwner(@RequestBody AdminAuthDto adminAuthDto) { + return authenticationService.authenticate(adminAuthDto) + .orElseThrow(() -> new AccessDeniedException("Wrong username or password")); + } + + @Operation(description = "Authenticates a fling user for a fling via code") + @ApiResponse(responseCode = "200", + description = "JWT Token authenticating the user for a fling") + @ApiResponse(responseCode = "403", + description = "Authentication failed, the provided code for the fling is wrong") + @ApiResponse(responseCode = "404", + description = "No fling for the given share id exists") + @PostMapping("/user") + public String authenticateUser(@RequestBody UserAuthDto userAuthDto) { + return authenticationService.authenticate(userAuthDto) + .orElseThrow(() -> new AccessDeniedException("Wrong username or password")); + } +} diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index bdd5163..43928e2 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -3,6 +3,7 @@ package net.friedl.fling.controller; import java.io.IOException; import java.util.List; import java.util.UUID; +import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; @@ -51,12 +52,13 @@ public class FlingController { } @PostMapping - public FlingDto postFling(@RequestBody FlingDto flingDto) { + public FlingDto postFling(@RequestBody @Valid FlingDto flingDto) { return flingService.create(flingDto); } @PostMapping("/{id}/artifact") - public ArtifactDto postArtifact(@PathVariable UUID id, @RequestBody ArtifactDto artifactDto) { + public ArtifactDto postArtifact(@PathVariable UUID id, + @RequestBody @Valid ArtifactDto artifactDto) { return artifactService.create(id, artifactDto); } @@ -76,7 +78,7 @@ public class FlingController { } @Operation(responses = { - @ApiResponse(responseCode = "200", + @ApiResponse(responseCode = "200", content = @Content( mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE, schema = @Schema(type = "string", format = "binary"))) diff --git a/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java b/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java index 8bf33c9..bd57543 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/OpenApiConfiguration.java @@ -46,7 +46,7 @@ public class OpenApiConfiguration { .scheme("bearer") .bearerFormat("JWT"); } - + public Info apiInfo() { return new Info() .contact(new Contact() diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/AdminAuthDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/AdminAuthDto.java new file mode 100644 index 0000000..31e981e --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/AdminAuthDto.java @@ -0,0 +1,21 @@ +package net.friedl.fling.model.dto; + +import javax.validation.constraints.NotNull; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +@AllArgsConstructor +@Builder +@Schema(name = "AdminAuth") +public class AdminAuthDto { + @NotNull + private String adminName; + + @NotNull + private String adminPassword; +} diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java index 0403e0b..0d2da8f 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java @@ -31,7 +31,6 @@ public class FlingDto { private Instant creationTime = Instant.now(); @Schema(description = "Share id of the fling. Used in the share link.") - @NotNull private String shareId; @Schema(description = "Authentication code for password protecting a fling.") diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/UserAuthDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/UserAuthDto.java new file mode 100644 index 0000000..255b908 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/UserAuthDto.java @@ -0,0 +1,21 @@ +package net.friedl.fling.model.dto; + +import javax.validation.constraints.NotNull; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +@AllArgsConstructor +@Builder +@Schema(name = "UserAuth") +public class UserAuthDto { + @NotNull + String shareId; + + @NotNull + String authCode; +} diff --git a/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java b/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java index be33cfd..03406dc 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java +++ b/service/fling/src/main/java/net/friedl/fling/model/json/PathDeserializer.java @@ -27,7 +27,7 @@ public class PathDeserializer extends StdDeserializer { ObjectCodec codec = p.getCodec(); JsonNode node = codec.readTree(p); - + return Paths.get(node.textValue()); } diff --git a/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java b/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java index cc25022..5d3a227 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java +++ b/service/fling/src/main/java/net/friedl/fling/model/json/PathSerializer.java @@ -19,7 +19,8 @@ public class PathSerializer extends StdSerializer { private static final long serialVersionUID = -1003917305429893614L; @Override - public void serialize(Path value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Path value, JsonGenerator gen, SerializerProvider provider) + throws IOException { gen.writeString(value.toString()); } diff --git a/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java b/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java index b9091a2..aa917e3 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java +++ b/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java @@ -8,8 +8,10 @@ import net.friedl.fling.persistence.entities.ArtifactEntity; @Mapper(componentModel = "spring") public interface ArtifactMapper { ArtifactDto map(ArtifactEntity artifactEntity); + ArtifactEntity map(ArtifactDto artifactDto); List mapEntities(List artifactEntities); + List mapDtos(List artifactDtos); } diff --git a/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java b/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java index 32b5184..7284ee3 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java +++ b/service/fling/src/main/java/net/friedl/fling/model/mapper/FlingMapper.java @@ -8,8 +8,10 @@ import net.friedl.fling.persistence.entities.FlingEntity; @Mapper(componentModel = "spring") public interface FlingMapper { FlingDto map(FlingEntity flingEntity); + FlingEntity map(FlingDto flingDto); List mapEntities(List flingEntities); + List mapDtos(List flingDtos); } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java index 8cf5dca..1c3b6dc 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java @@ -14,7 +14,8 @@ import lombok.Setter; @Entity @Table(name = "Artifact") -@Getter @Setter +@Getter +@Setter public class ArtifactEntity { @Id @GeneratedValue @@ -25,7 +26,7 @@ public class ArtifactEntity { @Column(nullable = false) private Instant uploadTime = Instant.now(); - + @Column(unique = true, nullable = true) private String archiveId; diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java index afcd94d..a3f46e5 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java @@ -15,7 +15,8 @@ import lombok.Setter; @Entity @Table(name = "Fling") -@Getter @Setter +@Getter +@Setter public class FlingEntity { @Id @GeneratedValue diff --git a/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java deleted file mode 100644 index 05cf99c..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.friedl.fling.security; - -import java.util.UUID; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.stereotype.Service; -import lombok.extern.slf4j.Slf4j; -import net.friedl.fling.security.authentication.FlingToken; -import net.friedl.fling.security.authentication.dto.UserAuthDto; -import net.friedl.fling.service.FlingService; - -@Slf4j -@Service -public class AuthorizationService { - private FlingService flingService; - - @Autowired - public AuthorizationService(FlingService flingService) { - this.flingService = flingService; - } - - public boolean allowUpload(UUID flingId, AbstractAuthenticationToken token) { - if (!(token instanceof FlingToken)) { - log.warn("Authorization attempt without fling token for {}. Authorization denied.", flingId); - return false; - } - - FlingToken flingToken = (FlingToken) token; - if (FlingAuthority.FLING_OWNER.name() - .equals(flingToken.getGrantedFlingAuthority().getAuthority())) { - log.debug("Owner authorized for upload [id = {}]", flingId); - return true; - } - - boolean uploadAllowed = flingService.getById(flingId).getAllowUpload(); - boolean authorized = uploadAllowed - && flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); - - log.debug("User {} authorized for upload [id = {}]", authorized ? "" : "not", flingId); - - return authorized; - } - - public boolean allowFlingAccess(UUID flingId, AbstractAuthenticationToken token) { - if (!(token instanceof FlingToken)) { - log.warn("Authorization attempt without fling token for {}. Authorization denied.", flingId); - return false; - } - - FlingToken flingToken = (FlingToken) token; - if (FlingAuthority.FLING_OWNER.name() - .equals(flingToken.getGrantedFlingAuthority().getAuthority())) { - log.debug("Owner authorized for fling access [id = {}]", flingId); - return true; - } - - boolean authorized = flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId); - log.debug("User {} authorized for fling access [id = {}]", authorized ? "" : "not", flingId); - - return authorized; - } - - public boolean allowFlingAccess(UserAuthDto userAuth, String shareId) { - boolean authorized = userAuth.getShareId().equals(shareId); - log.debug("User {} authorized for fling access [shareId = {}]", authorized ? "" : "not", - shareId); - - return authorized; - } - -} diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java b/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java new file mode 100644 index 0000000..3dd4698 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java @@ -0,0 +1,32 @@ +package net.friedl.fling.security; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public enum FlingAuthorities { + FLING_ADMIN("admin"), FLING_USER("user"); + + String authority; + + FlingAuthorities(String authority) { + this.authority = authority; + } + + public boolean verify(String authority) { + return this.authority.equals(authority); + } + + public boolean verify(AbstractAuthenticationToken authenticationToken) { + return authenticationToken.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .anyMatch(this.authority::equals); + } + + public boolean verify(GrantedAuthority grantedAuthority) { + return this.authority.equals(grantedAuthority.getAuthority()); + } + + public String getAuthority() { + return authority; + } +} diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/FlingAuthority.java deleted file mode 100644 index 78b7f56..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingAuthority.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.friedl.fling.security; - -public enum FlingAuthority { - FLING_OWNER, FLING_USER -} diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index 69580f4..e3d855c 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -3,6 +3,7 @@ package net.friedl.fling.security; import static org.springframework.security.config.Customizer.withDefaults; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -16,7 +17,9 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.FlingSecurityConfiguration; import net.friedl.fling.security.authentication.JwtAuthenticationFilter; +import net.friedl.fling.service.AuthorizationService; @Slf4j @Configuration @@ -24,9 +27,11 @@ import net.friedl.fling.security.authentication.JwtAuthenticationFilter; @Getter @Setter public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { + @Value("fling.security.allowedOrigins") + private List allowedOrigins; + private JwtAuthenticationFilter jwtAuthenticationFilter; private AuthorizationService authorizationService; - private FlingSecurityConfiguration securityConfiguration; @Autowired public FlingWebSecurityConfigurer(JwtAuthenticationFilter jwtAuthenticationFilter, @@ -35,7 +40,6 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.authorizationService = authorizationService; - this.securityConfiguration = securityConfiguraiton; } @Override @@ -91,7 +95,7 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { // And lastly, the owner is allowed everything .authorizeRequests() .antMatchers("/api/**") - .hasAuthority(FlingAuthority.FLING_OWNER.name()); + .hasAuthority(FlingAuthorities.FLING_ADMIN.getAuthority()); //@formatter:on } @@ -100,10 +104,10 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { public CorsConfigurationSource corsConfigurationSource() { // see https://stackoverflow.com/a/43559266 - log.info("Allowed origins: {}", securityConfiguration.getAllowedOrigins()); + log.info("Allowed origins: {}", allowedOrigins); CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(securityConfiguration.getAllowedOrigins()); + configuration.setAllowedOrigins(allowedOrigins); configuration.setAllowedMethods(List.of("*")); // setAllowCredentials(true) is important, otherwise: diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java deleted file mode 100644 index d9b1e30..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationController.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.friedl.fling.security.authentication; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import io.swagger.v3.oas.annotations.tags.Tag; -import net.friedl.fling.security.authentication.dto.OwnerAuthDto; -import net.friedl.fling.security.authentication.dto.UserAuthDto; - -@RestController -@RequestMapping("/api/auth") -@Tag(name = "auth", description = "Operations on /api/auth") -public class AuthenticationController { - - private AuthenticationService authenticationService; - - @Autowired - public AuthenticationController(AuthenticationService authenticationService) { - this.authenticationService = authenticationService; - } - - @PostMapping(path = "/owner") - public String authenticateOwner(@RequestBody OwnerAuthDto ownerAuthDto) { - return authenticationService.authenticate(ownerAuthDto); - } - - @PostMapping("/user") - public String authenticateUser(@RequestBody UserAuthDto userAuthDto) { - return authenticationService.authenticate(userAuthDto); - } -} diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java deleted file mode 100644 index a8bc181..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/AuthenticationService.java +++ /dev/null @@ -1,101 +0,0 @@ -package net.friedl.fling.security.authentication; - -import java.security.Key; -import java.time.Instant; -import java.util.Date; -import java.util.UUID; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.JwtParser; -import io.jsonwebtoken.Jwts; -import net.friedl.fling.model.dto.FlingDto; -import net.friedl.fling.security.FlingAuthority; -import net.friedl.fling.security.FlingSecurityConfiguration; -import net.friedl.fling.security.authentication.dto.OwnerAuthDto; -import net.friedl.fling.security.authentication.dto.UserAuthDto; -import net.friedl.fling.service.FlingService; - -@Service -public class AuthenticationService { - private FlingService flingService; - private JwtParser jwtParser; - private Key signingKey; - private FlingSecurityConfiguration securityConfig; - - @Autowired - public AuthenticationService(JwtParser jwtParser, Key signingKey, FlingService flingService, - FlingSecurityConfiguration securityConfig) { - this.flingService = flingService; - this.jwtParser = jwtParser; - this.signingKey = signingKey; - this.securityConfig = securityConfig; - } - - public String authenticate(OwnerAuthDto ownerAuth) { - if (!securityConfig.getAdminUser().equals(ownerAuth.getUsername())) { - throw new AccessDeniedException("Wrong credentials"); - } - - if (!securityConfig.getAdminPassword().equals(ownerAuth.getPassword())) { - throw new AccessDeniedException("Wrong credentials"); - } - - return makeBaseBuilder() - .setSubject("owner") - .compact(); - } - - public String authenticate(UserAuthDto userAuth) { - FlingDto flingDto = flingService.getByShareId(userAuth.getShareId()); - String authCode = userAuth.getCode(); - - if (!flingService.validateAuthCode(flingDto.getId(), authCode)) { - throw new AccessDeniedException("Wrong fling code"); - } - - return makeBaseBuilder() - .setSubject("user") - .claim("sid", flingDto.getShareId()) - .compact(); - - } - - public Authentication parseAuthentication(String token) { - Claims claims = parseClaims(token); - - FlingAuthority authority; - UUID flingId; - - switch (claims.getSubject()) { - case "owner": - authority = FlingAuthority.FLING_OWNER; - flingId = null; - break; - case "user": - authority = FlingAuthority.FLING_USER; - String sid = claims.get("sid", String.class); - flingId = flingService.getByShareId(sid).getId(); - break; - default: - throw new BadCredentialsException("Invalid token"); - } - - return new FlingToken(new GrantedFlingAuthority(authority, flingId)); - } - - private JwtBuilder makeBaseBuilder() { - return Jwts.builder() - .setIssuedAt(Date.from(Instant.now())) - .setExpiration(Date.from(Instant.now().plusSeconds(securityConfig.getJwtExpiration()))) - .signWith(signingKey); - } - - private Claims parseClaims(String token) { - return jwtParser.parseClaimsJws(token).getBody(); - } -} diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java new file mode 100644 index 0000000..1784d56 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java @@ -0,0 +1,15 @@ +package net.friedl.fling.security.authentication; + +import org.springframework.security.core.GrantedAuthority; +import net.friedl.fling.security.FlingAuthorities; + +public class FlingAdminAuthority implements GrantedAuthority { + + private static final long serialVersionUID = -4605768612393081070L; + + @Override + public String getAuthority() { + return FlingAuthorities.FLING_ADMIN.getAuthority(); + } + +} diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java index ccff7cd..e731e35 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java @@ -1,25 +1,36 @@ package net.friedl.fling.security.authentication; import java.util.List; +import java.util.UUID; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; public class FlingToken extends AbstractAuthenticationToken { private static final long serialVersionUID = -1112423505610346583L; - private GrantedFlingAuthority grantedFlingAuthority; + private String jwtToken; - public FlingToken(GrantedFlingAuthority authority) { + public FlingToken(GrantedAuthority authority, String jwtToken) { super(List.of(authority)); - this.grantedFlingAuthority = authority; + this.jwtToken = jwtToken; } - public GrantedFlingAuthority getGrantedFlingAuthority() { - return this.grantedFlingAuthority; + public boolean authorizedForFling(UUID id) { + for (GrantedAuthority grantedAuthority : getAuthorities()) { + if (grantedAuthority instanceof FlingAdminAuthority) return true; + + if (!(grantedAuthority instanceof FlingUserAuthority)) continue; + + UUID grantedFlingId = ((FlingUserAuthority) grantedAuthority).getFlingId(); + if (grantedFlingId.equals(id)) return true; + } + + return false; } @Override - public Object getCredentials() { - return null; + public String getCredentials() { + return this.jwtToken; } @Override diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java new file mode 100644 index 0000000..566eaf1 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java @@ -0,0 +1,25 @@ +package net.friedl.fling.security.authentication; + +import java.util.UUID; +import org.springframework.security.core.GrantedAuthority; +import net.friedl.fling.security.FlingAuthorities; + +public class FlingUserAuthority implements GrantedAuthority { + private static final long serialVersionUID = -1814514234042184275L; + + private UUID flingId; + + public FlingUserAuthority(UUID flingId) { + this.flingId = flingId; + } + + @Override + public String getAuthority() { + return FlingAuthorities.FLING_USER.getAuthority(); + } + + public UUID getFlingId() { + return flingId; + } + +} diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java deleted file mode 100644 index a56462f..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/GrantedFlingAuthority.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.friedl.fling.security.authentication; - -import java.util.UUID; -import org.springframework.security.core.GrantedAuthority; -import net.friedl.fling.security.FlingAuthority; - -/** - * Authority granting access to a fling - * - * @author Armin Friedl - */ -public class GrantedFlingAuthority implements GrantedAuthority { - - private static final long serialVersionUID = -1552301479158714777L; - - private FlingAuthority authority; - private UUID flingId; - - public GrantedFlingAuthority(FlingAuthority authority, UUID flingId) { - this.authority = authority; - this.flingId = flingId; - } - - public UUID getFlingId() { - return this.flingId; - } - - @Override - public String getAuthority() { - return authority.name(); - } - -} diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java index 71427d6..2ad3c5b 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java @@ -1,17 +1,19 @@ package net.friedl.fling.security.authentication; import java.io.IOException; +import java.util.stream.Collectors; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.service.AuthenticationService; @Slf4j @Component @@ -34,7 +36,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String header = request.getHeader(HEADER_STRING); if (header == null || !header.startsWith(TOKEN_PREFIX)) { - log.warn("Could not find bearer token. No JWT authentication."); + log.info("Anonymous request for {} {}?{}", request.getMethod(), request.getRequestURL(), + request.getQueryString()); filterChain.doFilter(request, response); return; } @@ -44,8 +47,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { SecurityContext securityContext = SecurityContextHolder.getContext(); if (securityContext.getAuthentication() == null) { - Authentication authentication = authenticationService.parseAuthentication(authToken); - securityContext.setAuthentication(authentication); + log.info("Authenticating request for {} {}?{}", request.getMethod(), request.getRequestURL(), + request.getQueryString()); + FlingToken token = authenticationService.parseAuthentication(authToken); + log.info("Authenticated as {}", token.getAuthorities().stream() + .map(GrantedAuthority::getAuthority).collect(Collectors.joining(","))); + securityContext.setAuthentication(token); } filterChain.doFilter(request, response); diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java deleted file mode 100644 index 606562f..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/OwnerAuthDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.friedl.fling.security.authentication.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Data -@Schema(name = "OwnerAuth") -public class OwnerAuthDto { - private String username; - private String password; -} diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java deleted file mode 100644 index b4a8107..0000000 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/dto/UserAuthDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.friedl.fling.security.authentication.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Data -@Schema(name = "UserAuth") -public class UserAuthDto { - String shareId; - String code; -} diff --git a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java index 0eadaa2..1a848de 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java @@ -62,12 +62,12 @@ public class ArtifactService { artifactEntity = artifactRepository.save(artifactEntity); return artifactMapper.map(artifactEntity); } - + /** * Deletes an artifact identified by {@code id}. NOOP if the artifact cannot be found. * * @param id An {@link UUID} that identifies the artifact - * @throws IOException If the deletion failed + * @throws IOException If the deletion failed */ public void delete(UUID id) throws IOException { archiveService.deleteArtifact(id); diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java new file mode 100644 index 0000000..ec35c5b --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java @@ -0,0 +1,116 @@ +package net.friedl.fling.service; + +import java.security.Key; +import java.time.Instant; +import java.util.Date; +import java.util.Optional; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.model.dto.AdminAuthDto; +import net.friedl.fling.model.dto.UserAuthDto; +import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.security.authentication.FlingAdminAuthority; +import net.friedl.fling.security.authentication.FlingToken; +import net.friedl.fling.security.authentication.FlingUserAuthority; + +@Slf4j +@Service +public class AuthenticationService { + private JwtParser jwtParser; + private Key jwtSigningKey; + private FlingRepository flingRepository; + private PasswordEncoder passwordEncoder; + + @Value("${fling.security.admin-name}") + private String adminName; + @Value("${fling.security.admin-password}") + private String adminPassword; + @Value("${fling.security.jwt-expiration}") + private Long jwtExpiration; + + @Autowired + public AuthenticationService(JwtParser jwtParser, Key jwtSigningKey, + PasswordEncoder passwordEncoder, FlingRepository flingRepository) { + + this.jwtParser = jwtParser; + this.jwtSigningKey = jwtSigningKey; + this.passwordEncoder = passwordEncoder; + this.flingRepository = flingRepository; + } + + public Optional authenticate(AdminAuthDto adminAuth) { + log.info("Authenticating {}", adminAuth.getAdminName()); + if (!adminName.equals(adminAuth.getAdminName())) { + log.debug("Authentication failed for {}", adminAuth.getAdminName()); + return Optional.empty(); + } + + if (!adminPassword.equals(adminAuth.getAdminPassword())) { + log.debug("Authentication failed for {}", adminAuth.getAdminName()); + return Optional.empty(); + } + + log.debug("Authentication successful for {}", adminAuth.getAdminName()); + return Optional.of( + getJwtBuilder() + .setSubject("admin") + .compact()); + } + + public Optional authenticate(UserAuthDto userAuth) { + log.info("Authenticating for fling [.shareId={}]", userAuth.getShareId()); + FlingEntity flingEntity = flingRepository.findByShareId(userAuth.getShareId()); + String providedAuthCodeHash = passwordEncoder.encode(userAuth.getAuthCode()); + String actualAuthCodeHash = flingEntity.getAuthCode(); + + if (!actualAuthCodeHash.equals(providedAuthCodeHash)) { + log.debug("Authentication failed for fling [.shareId={}]", userAuth.getShareId()); + return Optional.empty(); + } + + log.debug("Authentication successful for fling [.shareId={}]", userAuth.getShareId()); + return Optional.of( + getJwtBuilder() + .setSubject("user") + .claim("id", flingEntity.getId()) + .compact()); + + } + + public FlingToken parseAuthentication(String token) { + Claims claims = jwtParser.parseClaimsJws(token).getBody(); + + switch (claims.getSubject()) { + case "owner": + return new FlingToken(new FlingAdminAuthority(), token); + case "user": + UUID grantedFlingId = UUID.fromString(claims.get("id", String.class)); + return new FlingToken(new FlingUserAuthority(grantedFlingId), token); + default: + throw new BadCredentialsException("Invalid token"); + } + } + + /** + * Creates a new JwtBuilder. A new builder must be constructed for each JWT creation, because the + * builder keeps its state. + * + * @return A new JwtBuilder with basic default configuration. + */ + private JwtBuilder getJwtBuilder() { + return Jwts.builder() + .setIssuedAt(Date.from(Instant.now())) + .setExpiration(Date.from(Instant.now().plusSeconds(jwtExpiration))) + .signWith(jwtSigningKey); + } +} diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java new file mode 100644 index 0000000..55f6afb --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java @@ -0,0 +1,68 @@ +package net.friedl.fling.service; + +import java.util.UUID; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.security.FlingAuthorities; +import net.friedl.fling.security.authentication.FlingToken; + +@Slf4j +@Service +public class AuthorizationService { + private FlingRepository flingRepository; + + @Autowired + public AuthorizationService(FlingRepository flingRepository) { + this.flingRepository = flingRepository; + } + + public boolean allowUpload(UUID flingId, AbstractAuthenticationToken token) { + if (!(token instanceof FlingToken)) { + log.debug("Token of type {} not allowed. Authentication denied.", token.getClass()); + return false; + } + + if (FlingAuthorities.FLING_ADMIN.verify(token)) { + log.debug("Owner authorized for upload fling[.id={}]", flingId); + return true; + } + + if (!flingRepository.getOne(flingId).getAllowUpload()) { + log.debug("Fling[.id={}] does not not allow uploads"); + return false; + } + + FlingToken flingToken = (FlingToken) token; + if (flingToken.authorizedForFling(flingId)) { + log.debug("User authorized for upload fling[.id={}]", flingId); + return true; + } + + log.info("User not authorized for upload fling[.id={}]", flingId); + return false; + } + + public boolean allowFlingAccess(UUID flingId, AbstractAuthenticationToken token) { + if (!(token instanceof FlingToken)) { + log.debug("Token of type {} not allowed. Authentication denied.", token.getClass()); + return false; + } + + if (FlingAuthorities.FLING_ADMIN.verify(token)) { + log.debug("Owner authorized for fling access [id = {}]", flingId); + return true; + } + + FlingToken flingToken = (FlingToken) token; + if (flingToken.authorizedForFling(flingId)) { + log.debug("User authorized for fling access [id = {}]"); + return true; + } + + log.info("User not authorized to access fling[.id={}]", flingId); + return false; + } +} diff --git a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java index 2e074f2..5320f51 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java @@ -1,14 +1,13 @@ package net.friedl.fling.service; import java.io.IOException; -import java.security.MessageDigest; import java.util.Base64; import java.util.List; import java.util.UUID; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.KeyGenerators; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import lombok.extern.slf4j.Slf4j; @@ -26,17 +25,16 @@ public class FlingService { private FlingRepository flingRepository; private FlingMapper flingMapper; private ArchiveService archiveService; - private MessageDigest keyHashDigest; + private PasswordEncoder passwordEncoder; @Autowired public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, - ArchiveService archiveService, - MessageDigest keyHashDigest) { + ArchiveService archiveService, PasswordEncoder passwordEcoder) { this.flingRepository = flingRepository; this.flingMapper = flingMapper; this.archiveService = archiveService; - this.keyHashDigest = keyHashDigest; + this.passwordEncoder = passwordEcoder; } /** @@ -96,7 +94,7 @@ public class FlingService { public boolean validateAuthCode(UUID id, String authCode) { FlingEntity flingEntity = flingRepository.getOne(id); - if(StringUtils.hasText(flingEntity.getAuthCode()) != StringUtils.hasText(authCode)) { + if (StringUtils.hasText(flingEntity.getAuthCode()) != StringUtils.hasText(authCode)) { return false; // only one of them is empty; implicit null safety check } @@ -106,7 +104,7 @@ public class FlingService { } private String hashAuthCode(String authCode) { - String hash = new String(Hex.encode(keyHashDigest.digest(authCode.getBytes()))); + String hash = passwordEncoder.encode(authCode); log.debug("Hashed authentication code to {}", hash); return hash; } diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java index f1472c4..a2660bf 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/ArchiveService.java @@ -17,7 +17,7 @@ public interface ArchiveService { * @return An {@link InputStream} for reading the artifact */ InputStream getArtifact(UUID artifactId) throws IOException; - + /** * Retrieve a packaged fling from the archive * @@ -37,10 +37,10 @@ public interface ArchiveService { /** * Delete an artifact * - * @param id The unique artifact id + * @param id The unique artifact id */ void deleteArtifact(UUID artifactId) throws IOException; - + /** * Delete a fling * diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java index ffbb18e..4189a55 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java @@ -130,7 +130,7 @@ public class FileSystemArchive implements ArchiveService { Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath); Files.delete(zipDiskPath); - + artifactRepository.findAllByFlingId(flingId).forEach(ar -> ar.setArchived(false)); } } @@ -210,7 +210,7 @@ public class FileSystemArchive implements ArchiveService { public void setArchivePath(String archivePath) { this.archivePath = Paths.get(archivePath); } - + public void setArchivePath(Path archivePath) { this.archivePath = archivePath; } diff --git a/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json b/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json index 187790c..fd20a97 100644 --- a/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json +++ b/service/fling/src/main/resources/META-INF/spring-configuration-metadata.json @@ -48,7 +48,7 @@ "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" }, { - "name": "fling.security.admin-user", + "name": "fling.security.admin-name", "type": "java.lang.String", "description": "Username of the admin user/instance owner", "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" diff --git a/service/fling/src/main/resources/application-local.yml b/service/fling/src/main/resources/application-local.yml index c814456..84c1385 100644 --- a/service/fling/src/main/resources/application-local.yml +++ b/service/fling/src/main/resources/application-local.yml @@ -28,10 +28,10 @@ fling: - "http://localhost:3000" - "http://localhost:5000" - "http://10.0.2.2:5000" - admin-user: "${FLING_ADMIN_USER:admin}" - admin-password: "${FLING_ADMIN_PASSWORD:123}" - signing-key: "${FLING_SIGNING_KEY:changeitchangeitchangeitchangeit}" - jwt-expiration: "${FLING_JWT_EXPIRATION:180000}" + admin-name: "adminName" + admin-password: "adminPassword" + signing-key: "changeitchangeitchangeitchangeit" + jwt-expiration: "180000" api: version: "0" server-url: "http://localhost:8080" diff --git a/service/fling/src/main/resources/application-prod.yml b/service/fling/src/main/resources/application-prod.yml index edea956..9a19db4 100644 --- a/service/fling/src/main/resources/application-prod.yml +++ b/service/fling/src/main/resources/application-prod.yml @@ -19,9 +19,15 @@ fling: archive.filesystem.archive-path: "/var/fling/files" security: allowed-origins: - - "https://fling.friedl.net" + - "https://friedl.net" - "http://localhost:3000" - admin-user: "${FLING_ADMIN_USER:admin}" - admin-password: "${FLING_ADMIN_PASSWORD:123}" - signing-key: "${FLING_SIGNING_KEY:changeitchangeitchangeitchangeit}" - jwt-expiration: "${FLING_JWT_EXPIRATION:180000}" \ No newline at end of file + - "http://localhost:5000" + - "http://10.0.2.2:5000" + admin-name: "adminName" + admin-password: "adminPassword" + signing-key: "changeitchangeitchangeitchangeit" + jwt-expiration: "180000" + api: + version: "0" + server-url: "http://localhost:8080" + server-description: "API server for dev" \ No newline at end of file diff --git a/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java index c87264e..5f6ddd2 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/ArtifactControllerTest.java @@ -110,7 +110,8 @@ class ArtifactControllerTest { mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID)) .andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION)) - .andExpect(header().string(HttpHeaders.CONTENT_TYPE, not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, + not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) .andExpect(status().isNotFound()); } @@ -120,7 +121,8 @@ class ArtifactControllerTest { mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID)) .andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION)) - .andExpect(header().string(HttpHeaders.CONTENT_TYPE, not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, + not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE)))) .andExpect(status().isInternalServerError()); } diff --git a/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java new file mode 100644 index 0000000..5576e9e --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java @@ -0,0 +1,87 @@ +package net.friedl.fling.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.friedl.fling.model.dto.AdminAuthDto; +import net.friedl.fling.model.dto.UserAuthDto; +import net.friedl.fling.service.AuthenticationService; +import net.friedl.fling.service.AuthorizationService; + +@WebMvcTest(controllers = AuthenticationController.class, + includeFilters = {@Filter(Configuration.class)}) +@ActiveProfiles("local") +public class AuthenticationControllerTest { + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private AuthenticationService authenticationService; + + @MockBean + private AuthorizationService authorizationService; + + @Test + public void authenticateOwner_noToken_403() throws Exception { + AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "123"); + when(authenticationService.authenticate(any(AdminAuthDto.class))).thenReturn(Optional.empty()); + + mvc.perform(post("/api/auth/admin") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(adminAuthDto))) + .andExpect(status().is(403)); + } + + @Test + public void authenticateOwner_token_ok() throws Exception { + AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "123"); + when(authenticationService.authenticate(any(AdminAuthDto.class))) + .thenReturn(Optional.of("token")); + + mvc.perform(post("/api/auth/admin") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(adminAuthDto))) + .andExpect(status().is(200)) + .andExpect(content().string("token")); + } + + @Test + public void authenticateUser_noToken_403() throws Exception { + UserAuthDto userAuthDto = new UserAuthDto("shareId", "authCode"); + when(authenticationService.authenticate(any(UserAuthDto.class))).thenReturn(Optional.empty()); + + mvc.perform(post("/api/auth/user") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userAuthDto))) + .andExpect(status().is(403)); + } + + @Test + public void authenticateUser_token_ok() throws Exception { + UserAuthDto userAuthDto = new UserAuthDto("shareId", "authCode"); + when(authenticationService.authenticate(any(UserAuthDto.class))) + .thenReturn(Optional.of("token")); + + mvc.perform(post("/api/auth/user") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userAuthDto))) + .andExpect(status().is(200)) + .andExpect(content().string("token")); + } +} diff --git a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java index 3d0e80c..36edc34 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java @@ -96,6 +96,16 @@ public class FlingControllerTest { .andExpect(status().isOk()); } + @Test + public void postFling_validatesBody_notOk() throws Exception { + FlingDto invalidFlingDto = new FlingDto(); + + mockMvc.perform(post("/api/fling") + .content(mapper.writeValueAsString(invalidFlingDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + @Test public void postArtifact_ok() throws Exception { mockMvc.perform(post("/api/fling/{id}/artifact", flingId) @@ -104,6 +114,16 @@ public class FlingControllerTest { .andExpect(status().isOk()); } + @Test + public void postArtifact_validatesBody_notOk() throws Exception { + ArtifactDto invalidArtifactDto = new ArtifactDto(); + + mockMvc.perform(post("/api/fling/{id}/artifact", flingId) + .content(mapper.writeValueAsString(invalidArtifactDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + @Test public void getFling_noFlingWithId_notFound() throws Exception { doThrow(EntityNotFoundException.class).when(flingService).getById(flingId); diff --git a/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java b/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java index 3029211..6d6f2f5 100644 --- a/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java +++ b/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java @@ -75,8 +75,9 @@ public class FlingDtoTest { assertThat(violation.getMessage()).isEqualTo("must not be null"); } + @Test - void testSetShareId_null_validationFails() { + void testSetShareId_null_validationOk() { // must be nullable to support defaulting in service FlingDto flingDto = FlingDto.builder() .id(new UUID(0L, 0L)) .name("test") @@ -87,10 +88,7 @@ public class FlingDtoTest { Set> constraintViolations = validator.validate(flingDto); - assertThat(constraintViolations).hasSize(1); - ConstraintViolation violation = constraintViolations.iterator().next(); - assertThat(violation.getPropertyPath().toString()).isEqualTo("shareId"); - assertThat(violation.getMessage()).isEqualTo("must not be null"); + assertThat(constraintViolations).hasSize(0); } @Test diff --git a/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java index 95c49de..86eb932 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java @@ -62,7 +62,8 @@ public class ArtifactServiceTest { FlingRepository flingRepository, ArtifactMapper artifactMapper, ArchiveService archiveService) { - return new ArtifactService(artifactRepository, flingRepository, artifactMapper, archiveService); + return new ArtifactService(artifactRepository, flingRepository, artifactMapper, + archiveService); } } @@ -72,12 +73,12 @@ public class ArtifactServiceTest { artifactEntity1.setId(UUID.randomUUID()); artifactEntity1.setUploadTime(Instant.EPOCH); artifactEntity1.setPath(Path.of("artifact1")); - + this.artifactEntity2 = new ArtifactEntity(); artifactEntity2.setId(UUID.randomUUID()); artifactEntity2.setUploadTime(Instant.EPOCH.plus(12000, ChronoUnit.DAYS)); - artifactEntity2.setPath(Path.of("/","/sub","artifact2")); - + artifactEntity2.setPath(Path.of("/", "/sub", "artifact2")); + this.flingEntity = new FlingEntity(); flingEntity.setId(UUID.randomUUID()); flingEntity.setName("fling"); @@ -87,11 +88,11 @@ public class ArtifactServiceTest { @Override public FlingEntity answer(InvocationOnMock invocation) throws Throwable { FlingEntity flingEntity = invocation.getArgument(0); - if(flingEntity.getId() == null) flingEntity.setId(UUID.randomUUID()); + if (flingEntity.getId() == null) flingEntity.setId(UUID.randomUUID()); return flingEntity; } }); - + when(artifactRepository.save(any())).then(new Answer() { @Override public ArtifactEntity answer(InvocationOnMock invocation) throws Throwable { @@ -102,17 +103,17 @@ public class ArtifactServiceTest { }); } - + @Test public void getById_artifactExists_ok() { when(artifactRepository.getOne(artifactEntity1.getId())).thenReturn(artifactEntity1); - + ArtifactDto artifactDto = artifactService.getById(artifactEntity1.getId()); assertThat(artifactDto.getId(), equalTo(artifactEntity1.getId())); assertThat(artifactDto.getPath(), equalTo(artifactEntity1.getPath())); assertThat(artifactDto.getUploadTime(), equalTo(artifactEntity1.getUploadTime())); } - + @Test public void create_createsArtifact_ok() { ArtifactDto artifactToCreate = ArtifactDto.builder() @@ -121,15 +122,15 @@ public class ArtifactServiceTest { .build(); ArtifactDto createdArtifact = artifactService.create(flingEntity.getId(), artifactToCreate); - + assertThat(createdArtifact.getUploadTime(), equalTo(artifactToCreate.getUploadTime())); assertThat(createdArtifact.getPath(), equalTo(artifactToCreate.getPath())); } - + @Test public void delete_deletesArchiveAndArtifactEntry() throws IOException { artifactService.delete(artifactEntity1.getId()); - + verify(archiveService).deleteArtifact(artifactEntity1.getId()); verify(artifactRepository).deleteById(artifactEntity1.getId()); } diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java new file mode 100644 index 0000000..646e675 --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java @@ -0,0 +1,159 @@ +package net.friedl.fling.service; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import java.security.Key; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.impl.DefaultClaims; +import io.jsonwebtoken.impl.DefaultJws; +import io.jsonwebtoken.impl.DefaultJwsHeader; +import io.jsonwebtoken.security.Keys; +import net.friedl.fling.model.dto.AdminAuthDto; +import net.friedl.fling.model.dto.UserAuthDto; +import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.security.authentication.FlingAdminAuthority; +import net.friedl.fling.security.authentication.FlingToken; +import net.friedl.fling.security.authentication.FlingUserAuthority; + +@ExtendWith(SpringExtension.class) +@TestPropertySource("classpath:/application-test.properties") +@ActiveProfiles("test") +public class AuthenticationServiceTest { + @Autowired + public AuthenticationService authenticationService; + + @MockBean + private FlingRepository flingRepository; + + @MockBean + private JwtParser jwtParser; + + @MockBean + private PasswordEncoder passwordEncoder; + + @TestConfiguration + static class FlingServiceTestConfiguration { + private Key jwtSigningKey = Keys.hmacShaKeyFor(new byte[32]); + + @Bean + public AuthenticationService authenticationService(JwtParser jwtParser, + PasswordEncoder passwordEncoder, FlingRepository flingRepository) { + return new AuthenticationService(jwtParser, jwtSigningKey, passwordEncoder, flingRepository); + } + } + + @Test + public void authenticate_adminNameDiffers_empty() { + AdminAuthDto adminAuthDto = new AdminAuthDto("wrongadmin", "123"); + + assertThat(authenticationService.authenticate(adminAuthDto), equalTo(Optional.empty())); + } + + @Test + public void authenticate_passwordDiffers_empty() { + AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "wrongpassword"); + + assertThat(authenticationService.authenticate(adminAuthDto), equalTo(Optional.empty())); + } + + @Test + public void authenticate_ok() { + AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "123"); + + assertThat(authenticationService.authenticate(adminAuthDto), not(equalTo(Optional.empty()))); + } + + @Test + public void authenticate_authCodeDiffers_empty() { + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setAuthCode("test"); + flingEntity.setId(UUID.randomUUID()); + + UserAuthDto userAuthDto = new UserAuthDto("shareId", "wrongCode"); + + when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity); + when(passwordEncoder.encode(any(String.class))).thenReturn("wrongCode"); + + assertThat(authenticationService.authenticate(userAuthDto), equalTo(Optional.empty())); + } + + @Test + public void authenticate_authCodeEquals_ok() { + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setAuthCode("authCodeHash"); + flingEntity.setId(UUID.randomUUID()); + + UserAuthDto userAuthDto = UserAuthDto.builder() + .authCode("authCode") + .shareId("shareId").build(); + + when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity); + when(passwordEncoder.encode(any(String.class))).thenReturn("authCodeHash"); + + assertThat(authenticationService.authenticate(userAuthDto), not(equalTo(Optional.empty()))); + } + + @Test + public void parseAuthentication_owner_AdminAuthority() { + Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), + new DefaultClaims(Map.of("sub", "owner")), "signature"); + when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); + + FlingToken flingToken = authenticationService.parseAuthentication("any"); + assertThat(flingToken.isAuthenticated(), equalTo(true)); + // authorized for any fling + assertThat(flingToken.authorizedForFling(UUID.randomUUID()), equalTo(true)); + assertThat(flingToken.getCredentials(), equalTo("any")); + assertThat(flingToken.getAuthorities(), + hasItem(org.hamcrest.Matchers.any(FlingAdminAuthority.class))); + } + + @Test + public void parseAuthentication_user_UserAuthorityForId() { + Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), + new DefaultClaims(Map.of("sub", "user", "id", new UUID(0, 0).toString())), "signature"); + when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); + + FlingToken flingToken = authenticationService.parseAuthentication("any"); + assertThat(flingToken.isAuthenticated(), equalTo(true)); + // authorized for fling in token + assertThat(flingToken.authorizedForFling(new UUID(0, 0)), equalTo(true)); + // not authorized for fling other flings + assertThat(flingToken.authorizedForFling(new UUID(0, 1)), equalTo(false)); + assertThat(flingToken.getCredentials(), equalTo("any")); + assertThat(flingToken.getAuthorities(), + hasItem(org.hamcrest.Matchers.any(FlingUserAuthority.class))); + } + + @Test + public void parseAuthentication_unknownSubject_throws() { + Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), + new DefaultClaims(Map.of("sub", "unknownSubject")), "signature"); + when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); + + assertThrows(BadCredentialsException.class, + () -> authenticationService.parseAuthentication("any")); + } +} diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java new file mode 100644 index 0000000..5540941 --- /dev/null +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java @@ -0,0 +1,116 @@ +package net.friedl.fling.service; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.repositories.FlingRepository; +import net.friedl.fling.security.authentication.FlingAdminAuthority; +import net.friedl.fling.security.authentication.FlingToken; +import net.friedl.fling.security.authentication.FlingUserAuthority; + +@ExtendWith(SpringExtension.class) +public class AuthorizationServiceTest { + + @Autowired + private AuthorizationService authorizationService; + + @MockBean + private FlingRepository flingRepository; + + @TestConfiguration + static class FlingServiceTestConfiguration { + @Bean + public AuthorizationService authorizationService(FlingRepository flingRepository) { + return new AuthorizationService(flingRepository); + } + } + + @Test + public void allowUpload_unknownToken_false() { + var unkownToken = new AnonymousAuthenticationToken("key", "principal", + List.of(new SimpleGrantedAuthority("role"))); + + assertFalse(authorizationService.allowUpload(UUID.randomUUID(), unkownToken)); + } + + @Test + public void allowUpload_flingAdmin_true() { + FlingToken flingToken = new FlingToken(new FlingAdminAuthority(), "jwtToken"); + assertTrue(authorizationService.allowUpload(UUID.randomUUID(), flingToken)); + } + + @Test + public void allowUpload_noAdmin_uploadDisallowed_false() { + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setAllowUpload(false); + + FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + + when(flingRepository.getOne(new UUID(0, 0))).thenReturn(flingEntity); + + assertFalse(authorizationService.allowUpload(new UUID(0, 0), flingToken)); + } + + @Test + public void allowUpload_noAdmin_uploadAllowed_notAuthorized_false() { + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setAllowUpload(true); + + FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + + when(flingRepository.getOne(new UUID(1, 1))).thenReturn(flingEntity); + + // Token: UUID(0,0), Request: UUID(1,1) + assertFalse(authorizationService.allowUpload(new UUID(1, 1), flingToken)); + } + + @Test + public void allowUpload_noAdmin_uploadAllowed_authorized_true() { + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setAllowUpload(true); + + FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + + when(flingRepository.getOne(new UUID(0, 0))).thenReturn(flingEntity); + + // Token: UUID(0,0), Request: UUID(0,0) + assertTrue(authorizationService.allowUpload(new UUID(0, 0), flingToken)); + } + + @Test + public void allowFlingAccess_unknownToken_false() { + var unkownToken = new AnonymousAuthenticationToken("key", "principal", + List.of(new SimpleGrantedAuthority("role"))); + assertFalse(authorizationService.allowFlingAccess(UUID.randomUUID(), unkownToken)); + } + + @Test + public void allowFlingAcess_flingAdmin_true() { + FlingToken flingToken = new FlingToken(new FlingAdminAuthority(), "jwtToken"); + assertTrue(authorizationService.allowFlingAccess(UUID.randomUUID(), flingToken)); + } + + @Test + public void allowFlingAcess_flingUser_notAuthorizedForId_false() { + FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + assertFalse(authorizationService.allowFlingAccess(new UUID(1, 1), flingToken)); + } + + @Test + public void allowFlingAcess_flingUser_authorizedForId_true() { + FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + assertTrue(authorizationService.allowFlingAccess(new UUID(0, 0), flingToken)); + } +} diff --git a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java index df1d1a7..524f8a2 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java @@ -11,8 +11,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.List; import java.util.UUID; @@ -25,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; -import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.junit.jupiter.SpringExtension; import net.friedl.fling.model.dto.FlingDto; import net.friedl.fling.model.mapper.FlingMapper; @@ -38,12 +36,12 @@ import net.friedl.fling.service.archive.ArchiveService; public class FlingServiceTest { @Autowired private FlingService flingService; - + @Autowired private FlingMapper flingMapper; - @Autowired - private MessageDigest keyHashDigest; + @MockBean + private PasswordEncoder passwordEncoder; @MockBean private FlingRepository flingRepository; @@ -61,16 +59,11 @@ public class FlingServiceTest { public FlingMapper flingMapper() { return new FlingMapperImpl(); } - - @Bean - public MessageDigest keyHashDigest() throws NoSuchAlgorithmException { - return MessageDigest.getInstance("SHA-512"); - } @Bean public FlingService flingService(FlingRepository flingRepository, FlingMapper flingMapper, - ArchiveService archiveService, MessageDigest keyHashDigest) { - return new FlingService(flingRepository, flingMapper, archiveService, keyHashDigest); + ArchiveService archiveService, PasswordEncoder passwordEncoder) { + return new FlingService(flingRepository, flingMapper, archiveService, passwordEncoder); } } @@ -79,7 +72,7 @@ public class FlingServiceTest { this.flingEntity1 = new FlingEntity(); flingEntity1.setId(UUID.randomUUID()); flingEntity1.setName("fling1"); - flingEntity1.setAuthCode(new String(Hex.encode(keyHashDigest.digest("authCode1".getBytes())))); + flingEntity1.setAuthCode("testhash"); flingEntity1.setCreationTime(Instant.now()); this.flingEntity2 = new FlingEntity(); @@ -94,7 +87,8 @@ public class FlingServiceTest { FlingEntity flingEntity = invocation.getArgument(0); flingEntity.setId(UUID.randomUUID()); return flingEntity; - }}); + } + }); } @Test @@ -131,11 +125,11 @@ public class FlingServiceTest { public void create_hasAuthCode_setAuthCode() { FlingDto flingDto = new FlingDto(); flingDto.setAuthCode("test"); - - String hashedAuthCode = new String(Hex.encode(keyHashDigest.digest(flingDto.getAuthCode().getBytes()))); - + + when(passwordEncoder.encode(any(String.class))).thenReturn("testhash"); + FlingDto createdFling = flingService.create(flingDto); - assertThat(createdFling.getAuthCode(), is(hashedAuthCode)); + assertThat(createdFling.getAuthCode(), is("testhash")); } @Test @@ -146,35 +140,36 @@ public class FlingServiceTest { FlingDto createdFling = flingService.create(flingDto); assertThat(createdFling.getShareId(), is("test")); } - + @Test public void getByShareId_flingDto() { when(flingRepository.findByShareId("shareId2")).thenReturn(flingEntity2); - + FlingDto foundFling = flingService.getByShareId("shareId2"); assertThat(foundFling.getShareId(), equalTo("shareId2")); } - + @Test public void delete_deletesFromArchiveAndDb() throws IOException { UUID testId = UUID.randomUUID(); flingService.delete(testId); - + verify(archiveService).deleteFling(testId); verify(flingRepository).deleteById(testId); } - + @Test public void validateAuthCode_codesMatch_true() { when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1); - + when(passwordEncoder.encode("authCode1")).thenReturn("testhash"); + assertThat(flingService.validateAuthCode(flingEntity1.getId(), "authCode1"), is(true)); } - + @Test public void validateAuthCode_codesDoNotMatch_false() { - when(flingRepository.getOne(flingEntity2.getId())).thenReturn(flingEntity2); - + when(flingRepository.getOne(flingEntity2.getId())).thenReturn(flingEntity2); + assertThat(flingService.validateAuthCode(flingEntity2.getId(), "authCode1"), is(false)); } diff --git a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java index fc34e53..e5554b7 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java @@ -162,7 +162,8 @@ public class FileSystemArchiveTest { @Test public void storeArtifact_setsArchivedTrue() throws IOException, URISyntaxException { InputStream artifact2Stream = new FileInputStream( - new File(getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI())); + new File( + getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI())); when(artifactRepository.getOne(artifactEntity2.getId())).thenReturn(artifactEntity2); fileSystemArchive.storeArtifact(artifactEntity2.getId(), artifact2Stream); @@ -175,11 +176,12 @@ public class FileSystemArchiveTest { @Test public void storeArtifact_storesArtifactToFlingDisk() throws URISyntaxException, IOException { InputStream artifact2Stream = new FileInputStream( - new File(getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI())); + new File( + getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI())); when(artifactRepository.getOne(artifactEntity2.getId())).thenReturn(artifactEntity2); fileSystemArchive.storeArtifact(artifactEntity2.getId(), artifact2Stream); - + artifact2Stream.close(); InputStream flingStream = diff --git a/service/fling/src/test/resources/application-test.properties b/service/fling/src/test/resources/application-test.properties new file mode 100644 index 0000000..4a61042 --- /dev/null +++ b/service/fling/src/test/resources/application-test.properties @@ -0,0 +1,3 @@ +fling.security.jwt-expiration=18000 +fling.security.admin-name=admin +fling.security.admin-password=123 \ No newline at end of file From 3be61c4fa129ccad47ba6f3a2a2ac8474f37abe6 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sat, 11 Jul 2020 18:37:50 +0200 Subject: [PATCH 07/30] API Access Configuration, get Artifacts by fling id --- .../fling/controller/FlingController.java | 8 +- .../fling/model/mapper/ArtifactMapper.java | 3 + .../repositories/FlingRepository.java | 4 +- .../security/FlingWebSecurityConfigurer.java | 86 ++++++++++++------- .../fling/service/AuthenticationService.java | 2 +- .../fling/service/AuthorizationService.java | 17 ++++ .../friedl/fling/service/FlingService.java | 12 +++ .../fling/controller/FlingControllerTest.java | 43 +++++++++- .../service/AuthenticationServiceTest.java | 2 +- .../fling/service/FlingServiceTest.java | 48 ++++++++++- 10 files changed, 186 insertions(+), 39 deletions(-) diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index 43928e2..ba9a11c 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -2,6 +2,7 @@ package net.friedl.fling.controller; import java.io.IOException; import java.util.List; +import java.util.Set; import java.util.UUID; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -56,12 +57,17 @@ public class FlingController { return flingService.create(flingDto); } - @PostMapping("/{id}/artifact") + @PostMapping("/{id}/artifacts") public ArtifactDto postArtifact(@PathVariable UUID id, @RequestBody @Valid ArtifactDto artifactDto) { return artifactService.create(id, artifactDto); } + @GetMapping("/{id}/artifacts") + public Set getArtifacts(@PathVariable UUID id) { + return flingService.getArtifacts(id); + } + @GetMapping(path = "/{id}") public FlingDto getFling(@PathVariable UUID id) { return flingService.getById(id); diff --git a/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java b/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java index aa917e3..fc2be15 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java +++ b/service/fling/src/main/java/net/friedl/fling/model/mapper/ArtifactMapper.java @@ -1,6 +1,7 @@ package net.friedl.fling.model.mapper; import java.util.List; +import java.util.Set; import org.mapstruct.Mapper; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.persistence.entities.ArtifactEntity; @@ -13,5 +14,7 @@ public interface ArtifactMapper { List mapEntities(List artifactEntities); + Set mapEntities(Set artifactEntities); + List mapDtos(List artifactDtos); } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java index 59d2248..99ce0e6 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java @@ -11,6 +11,6 @@ public interface FlingRepository extends JpaRepository { FlingEntity findByShareId(String shareId); - @Query("SELECT COUNT(*) FROM ArtifactEntity a, FlingEntity f where a.fling=f.id and f.id=:flingId") - Long countArtifactsById(Long flingId); + @Query("SELECT fe FROM FlingEntity fe JOIN ArtifactEntity ae ON fe.id=ae.id WHERE ae.id=:artifactId") + FlingEntity findByArtifactId(UUID artifactId); } diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index e3d855c..5658bf1 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -1,5 +1,6 @@ package net.friedl.fling.security; +import static net.friedl.fling.security.FlingAuthorities.FLING_ADMIN; import static org.springframework.security.config.Customizer.withDefaults; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -10,6 +11,7 @@ import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -45,57 +47,79 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //@formatter:off - http + http .csrf().disable() .cors(withDefaults()) + + /**********************************************/ + /** Authentication Interceptor Configuration **/ + /**********************************************/ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - // Everybody can try to authenticate + // Do not keep authorization token in session. This would interfere with bearer authentication + // in that it is possible to authenticate without a bearer token if the session is kept. + // Turn off this confusing and non-obvious behavior. + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + + + /*************************************/ + /** API Authorization Configuration **/ + /*************************************/ + //! Go from most specific to more !// + //! general, as first hit counts !// + + /**********************************/ + /** Authorization for: /api/auth **/ + /**********************************/ .authorizeRequests() .antMatchers("/api/auth/**") .permitAll() .and() - // We need to go from most specific to more general. - // Hence, first define user permissions + + + /***********************************/ + /** Authorization for: /api/fling **/ + /***********************************/ .authorizeRequests() - // TODO: This is still insecure since URLs are not encrypted - // TODO: iframe requests don't send the bearer, use cookie instead - .antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download/{downloadId}") - .permitAll() + .antMatchers(HttpMethod.GET, "/api/fling/{flingId}/**") + .access("@authorizationService.allowFlingAccess(#flingId, authentication)") .and() .authorizeRequests() - .antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**") - .access("@authorizationService.allowUpload(#flingId, authentication)") + .antMatchers(HttpMethod.GET, "/api/fling/share/{shareId}") + .access("@authorizationService.allowFlingAccessByShareId(#shareId, authentication)") .and() .authorizeRequests() - .antMatchers(HttpMethod.PATCH, "/api/artifacts/{artifactId}") - .access("@authorizationService.allowPatchingArtifact(#artifactId, authentication)") + .antMatchers(HttpMethod.POST, "/api/fling/{flingId}/artifact") + .access("@authorizationService.allowUpload(#flingId, authentication)") + .and() + // only admin can create, delete and list flings + .authorizeRequests() + .antMatchers(HttpMethod.DELETE, "/api/fling/{flingId}") + .hasAnyAuthority(FLING_ADMIN.getAuthority()) .and() .authorizeRequests() - // TODO: This is still insecure since URLs are not encrypted - // TODO: iframe requests don't send the bearer, use cookie instead - .antMatchers("/api/artifacts/{artifactId}/{downloadId}/download") - .permitAll() + .antMatchers(HttpMethod.POST, "/api/fling") + .hasAuthority(FLING_ADMIN.getAuthority()) .and() .authorizeRequests() - // TODO: Security by request parameters is just not well supported with spring security - // TODO: Change API - .regexMatchers(HttpMethod.GET, "\\/api\\/fling\\?(shareId=|flingId=)[a-zA-Z0-9]+") - .access("@authorizationService.allowFlingAccess(authentication, request)") + .antMatchers(HttpMethod.GET, "/api/fling") + .hasAuthority(FLING_ADMIN.getAuthority()) + .and() + + + /***************************************/ + /** Authorization for: /api/artifacts **/ + /***************************************/ + .authorizeRequests() + .antMatchers(HttpMethod.GET, "/api/artifacts/{artifactId}/**") + .access("@authorizationService.allowArtifactAccess(#artifactId, token)") .and() .authorizeRequests() - // TODO: Security by request parameters is just not well supported with spring security - // TODO: Change API - .regexMatchers(HttpMethod.GET, "\\/api\\/artifacts\\?(shareId=|flingId=)[a-zA-Z0-9]+") - .access("@authorizationService.allowFlingAccess(authentication, request)") + .antMatchers(HttpMethod.POST, "/api/artifacts/{artifactId}/data") + .access("@authorizationService.allowArtifactUpload(#artifactId, token)") .and() .authorizeRequests() - .antMatchers(HttpMethod.GET, "/api/fling/{flingId}/**") - .access("@authorizationService.allowFlingAccess(#flingId, authentication)") - .and() - // And lastly, the owner is allowed everything - .authorizeRequests() - .antMatchers("/api/**") - .hasAuthority(FlingAuthorities.FLING_ADMIN.getAuthority()); + .antMatchers(HttpMethod.DELETE, "/api/artifacts/{artifactId}") + .access("@authorizationService.allowArtifactUpload(#artifactId, token)"); //@formatter:on } diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java index ec35c5b..afe15f1 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java @@ -91,7 +91,7 @@ public class AuthenticationService { Claims claims = jwtParser.parseClaimsJws(token).getBody(); switch (claims.getSubject()) { - case "owner": + case "admin": return new FlingToken(new FlingAdminAuthority(), token); case "user": UUID grantedFlingId = UUID.fromString(claims.get("id", String.class)); diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java index 55f6afb..d1a151d 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.persistence.entities.FlingEntity; import net.friedl.fling.persistence.repositories.FlingRepository; import net.friedl.fling.security.FlingAuthorities; import net.friedl.fling.security.authentication.FlingToken; @@ -65,4 +66,20 @@ public class AuthorizationService { log.info("User not authorized to access fling[.id={}]", flingId); return false; } + + public boolean allowFlingAccessByShareId(String shareId, AbstractAuthenticationToken token) { + FlingEntity flingEntity = flingRepository.findByShareId(shareId); + return allowFlingAccess(flingEntity.getId(), token); + } + + public boolean allowArtifactAccess(UUID artifactId, AbstractAuthenticationToken token) { + FlingEntity flingEntity = flingRepository.findByArtifactId(artifactId); + return allowFlingAccess(flingEntity.getId(), token); + } + + public boolean allowArtifactUpload(UUID artifactId, AbstractAuthenticationToken token) { + FlingEntity flingEntity = flingRepository.findByArtifactId(artifactId); + return allowUpload(flingEntity.getId(), token); + } + } diff --git a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java index 5320f51..2bf418c 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java @@ -3,6 +3,7 @@ package net.friedl.fling.service; import java.io.IOException; import java.util.Base64; import java.util.List; +import java.util.Set; import java.util.UUID; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; @@ -11,7 +12,9 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.FlingDto; +import net.friedl.fling.model.mapper.ArtifactMapper; import net.friedl.fling.model.mapper.FlingMapper; import net.friedl.fling.persistence.entities.FlingEntity; import net.friedl.fling.persistence.repositories.FlingRepository; @@ -24,15 +27,18 @@ public class FlingService { private FlingRepository flingRepository; private FlingMapper flingMapper; + private ArtifactMapper artifactMapper; private ArchiveService archiveService; private PasswordEncoder passwordEncoder; @Autowired public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, + ArtifactMapper artifactMapper, ArchiveService archiveService, PasswordEncoder passwordEcoder) { this.flingRepository = flingRepository; this.flingMapper = flingMapper; + this.artifactMapper = artifactMapper; this.archiveService = archiveService; this.passwordEncoder = passwordEcoder; } @@ -92,6 +98,12 @@ public class FlingService { log.debug("Deleted fling {}", id); } + public Set getArtifacts(UUID id) { + FlingEntity flingEntity = flingRepository.getOne(id); + Set artifactDto = artifactMapper.mapEntities(flingEntity.getArtifacts()); + return artifactDto == null ? Set.of() : artifactDto; + } + public boolean validateAuthCode(UUID id, String authCode) { FlingEntity flingEntity = flingRepository.getOne(id); if (StringUtils.hasText(flingEntity.getAuthCode()) != StringUtils.hasText(authCode)) { diff --git a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java index 36edc34..1860f13 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java @@ -1,6 +1,7 @@ package net.friedl.fling.controller; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; import static org.mockito.ArgumentMatchers.any; @@ -20,6 +21,7 @@ import java.io.IOException; import java.nio.file.Path; import java.time.Instant; import java.util.List; +import java.util.Set; import java.util.UUID; import javax.persistence.EntityNotFoundException; import org.hamcrest.Matchers; @@ -108,7 +110,7 @@ public class FlingControllerTest { @Test public void postArtifact_ok() throws Exception { - mockMvc.perform(post("/api/fling/{id}/artifact", flingId) + mockMvc.perform(post("/api/fling/{id}/artifacts", flingId) .content(mapper.writeValueAsString(artifactDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); @@ -118,12 +120,49 @@ public class FlingControllerTest { public void postArtifact_validatesBody_notOk() throws Exception { ArtifactDto invalidArtifactDto = new ArtifactDto(); - mockMvc.perform(post("/api/fling/{id}/artifact", flingId) + mockMvc.perform(post("/api/fling/{id}/artifacts", flingId) .content(mapper.writeValueAsString(invalidArtifactDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } + @Test + public void getArtifact_noFlingWithId_notFound() throws Exception { + doThrow(EntityNotFoundException.class).when(flingService).getArtifacts(flingId); + + mockMvc.perform(get("/api/fling/{id}/artifacts", flingId)) + .andExpect(status().isNotFound()); + } + + @Test + public void getArtifact_flingFound_noArtifacts_emptySet() throws Exception { + when(flingService.getArtifacts(flingId)).thenReturn(Set.of()); + + mockMvc.perform(get("/api/fling/{id}/artifacts", flingId)) + .andExpect(status().isOk()) + .andExpect(content().string(equalTo("[]"))); + } + + @Test + public void getArtifact_flingFound_hasArtifacts_returnArtifacts() throws Exception { + ArtifactDto artifactDto1 = ArtifactDto.builder() + .id(new UUID(0, 0)) + .build(); + + ArtifactDto artifactDto2 = ArtifactDto.builder() + .id(new UUID(0, 1)) + .build(); + + when(flingService.getArtifacts(flingId)).thenReturn(Set.of(artifactDto1, artifactDto2)); + + mockMvc.perform(get("/api/fling/{id}/artifacts", flingId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", + anyOf(equalTo(new UUID(0, 0).toString()), equalTo(new UUID(0, 1).toString())))) + .andExpect(jsonPath("$[1].id", + anyOf(equalTo(new UUID(0, 0).toString()), equalTo(new UUID(0, 1).toString())))); + } + @Test public void getFling_noFlingWithId_notFound() throws Exception { doThrow(EntityNotFoundException.class).when(flingService).getById(flingId); diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java index 646e675..14181fa 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java @@ -118,7 +118,7 @@ public class AuthenticationServiceTest { @Test public void parseAuthentication_owner_AdminAuthority() { Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), - new DefaultClaims(Map.of("sub", "owner")), "signature"); + new DefaultClaims(Map.of("sub", "admin")), "signature"); when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); FlingToken flingToken = authenticationService.parseAuthentication("any"); diff --git a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java index 524f8a2..a765c5a 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java @@ -4,8 +4,10 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; @@ -13,7 +15,9 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.time.Instant; import java.util.List; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,9 +29,13 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.junit.jupiter.SpringExtension; +import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.FlingDto; +import net.friedl.fling.model.mapper.ArtifactMapper; +import net.friedl.fling.model.mapper.ArtifactMapperImpl; import net.friedl.fling.model.mapper.FlingMapper; import net.friedl.fling.model.mapper.FlingMapperImpl; +import net.friedl.fling.persistence.entities.ArtifactEntity; import net.friedl.fling.persistence.entities.FlingEntity; import net.friedl.fling.persistence.repositories.FlingRepository; import net.friedl.fling.service.archive.ArchiveService; @@ -60,10 +68,17 @@ public class FlingServiceTest { return new FlingMapperImpl(); } + @Bean + public ArtifactMapper ArtifactMapper() { + return new ArtifactMapperImpl(); + } + @Bean public FlingService flingService(FlingRepository flingRepository, FlingMapper flingMapper, + ArtifactMapper artifactMapper, ArchiveService archiveService, PasswordEncoder passwordEncoder) { - return new FlingService(flingRepository, flingMapper, archiveService, passwordEncoder); + return new FlingService(flingRepository, flingMapper, artifactMapper, archiveService, + passwordEncoder); } } @@ -158,6 +173,37 @@ public class FlingServiceTest { verify(flingRepository).deleteById(testId); } + @Test + public void getArtifacts_noArtifacts_emptySet() throws IOException { + UUID testId = UUID.randomUUID(); + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setId(testId); + flingEntity.setArtifacts(null); + + when(flingRepository.getOne(testId)).thenReturn(flingEntity); + + assertThat(flingService.getArtifacts(testId), is(empty())); + } + + @Test + public void getArtifacts_flingWithArtifacts_artifactSet() throws Exception { + UUID artifactId = UUID.randomUUID(); + ArtifactEntity artifactEntity = new ArtifactEntity(); + artifactEntity.setId(artifactId); + + UUID flingId = UUID.randomUUID(); + FlingEntity flingEntity = new FlingEntity(); + flingEntity.setId(flingId); + flingEntity.setArtifacts(Set.of(artifactEntity)); + + when(flingRepository.getOne(flingId)).thenReturn(flingEntity); + + Set artifacts = flingService.getArtifacts(flingId); + assertThat(artifacts, hasSize(1)); + assertThat(artifacts.stream().map(ArtifactDto::getId).collect(Collectors.toSet()), + contains(artifactId)); + } + @Test public void validateAuthCode_codesMatch_true() { when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1); From c663ec5e7362d9a737958f5fe47db6df8838d5da Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 00:10:43 +0200 Subject: [PATCH 08/30] Use standard generated hibernate time stamps --- .../net/friedl/fling/model/dto/ArtifactDto.java | 7 +++---- .../java/net/friedl/fling/model/dto/FlingDto.java | 5 ++--- .../persistence/entities/ArtifactEntity.java | 15 ++++++++++++--- .../fling/persistence/entities/FlingEntity.java | 15 +++++++++++++-- .../friedl/fling/service/ArtifactServiceTest.java | 8 -------- .../friedl/fling/service/FlingServiceTest.java | 3 --- .../service/archive/FileSystemArchiveTest.java | 6 ------ 7 files changed, 30 insertions(+), 29 deletions(-) diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java index be6b682..8dfcb90 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java @@ -26,10 +26,9 @@ public class ArtifactDto { @NotNull private Path path; - @Schema(type = "integer", format = "int64", - description = "Upload time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") - @Builder.Default - private Instant uploadTime = Instant.now(); + @Schema(type = "integer", format = "int64", accessMode = AccessMode.READ_ONLY, + description = "Creation time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") + private Instant creationTime; @Schema(accessMode = AccessMode.READ_ONLY, type = "boolean", description = "Whether the artifact was successfully persisted in the archive.") diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java index 0d2da8f..be6379d 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java @@ -24,11 +24,10 @@ public class FlingDto { @NotNull private String name; - @Schema(type = "integer", format = "int64", + @Schema(type = "integer", format = "int64", accessMode = AccessMode.READ_ONLY, description = "Creation time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") @NotNull - @Builder.Default - private Instant creationTime = Instant.now(); + private Instant creationTime; @Schema(description = "Share id of the fling. Used in the share link.") private String shareId; diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java index 1c3b6dc..3e9462d 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java @@ -9,6 +9,9 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.Version; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import lombok.Getter; import lombok.Setter; @@ -24,9 +27,6 @@ public class ArtifactEntity { @Column(nullable = false) private Path path; - @Column(nullable = false) - private Instant uploadTime = Instant.now(); - @Column(unique = true, nullable = true) private String archiveId; @@ -35,4 +35,13 @@ public class ArtifactEntity { @ManyToOne(optional = false) private FlingEntity fling; + + @CreationTimestamp + private Instant creationTime; + + @UpdateTimestamp + private Instant updateTime; + + @Version + private Long version; } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java index a3f46e5..54039b2 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/FlingEntity.java @@ -1,6 +1,7 @@ package net.friedl.fling.persistence.entities; import java.time.Instant; +import java.util.Date; import java.util.Set; import java.util.UUID; import javax.persistence.CascadeType; @@ -10,6 +11,9 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; +import javax.persistence.Version; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import lombok.Getter; import lombok.Setter; @@ -24,8 +28,6 @@ public class FlingEntity { private String name; - private Instant creationTime = Instant.now(); - private Instant expirationTime; private Integer expirationClicks; @@ -46,4 +48,13 @@ public class FlingEntity { @OneToMany(mappedBy = "fling", cascade = CascadeType.ALL, orphanRemoval = true) private Set artifacts; + + @CreationTimestamp + private Date creationTime; + + @UpdateTimestamp + private Date updateTime; + + @Version + private Long version; } diff --git a/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java index 86eb932..739f96b 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/ArtifactServiceTest.java @@ -7,8 +7,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.nio.file.Path; -import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -71,18 +69,15 @@ public class ArtifactServiceTest { public void beforeEach() { this.artifactEntity1 = new ArtifactEntity(); artifactEntity1.setId(UUID.randomUUID()); - artifactEntity1.setUploadTime(Instant.EPOCH); artifactEntity1.setPath(Path.of("artifact1")); this.artifactEntity2 = new ArtifactEntity(); artifactEntity2.setId(UUID.randomUUID()); - artifactEntity2.setUploadTime(Instant.EPOCH.plus(12000, ChronoUnit.DAYS)); artifactEntity2.setPath(Path.of("/", "/sub", "artifact2")); this.flingEntity = new FlingEntity(); flingEntity.setId(UUID.randomUUID()); flingEntity.setName("fling"); - flingEntity.setCreationTime(Instant.now()); when(flingRepository.save(any())).then(new Answer() { @Override @@ -111,19 +106,16 @@ public class ArtifactServiceTest { ArtifactDto artifactDto = artifactService.getById(artifactEntity1.getId()); assertThat(artifactDto.getId(), equalTo(artifactEntity1.getId())); assertThat(artifactDto.getPath(), equalTo(artifactEntity1.getPath())); - assertThat(artifactDto.getUploadTime(), equalTo(artifactEntity1.getUploadTime())); } @Test public void create_createsArtifact_ok() { ArtifactDto artifactToCreate = ArtifactDto.builder() - .uploadTime(Instant.now()) .path(Path.of("new", "artifacts")) .build(); ArtifactDto createdArtifact = artifactService.create(flingEntity.getId(), artifactToCreate); - assertThat(createdArtifact.getUploadTime(), equalTo(artifactToCreate.getUploadTime())); assertThat(createdArtifact.getPath(), equalTo(artifactToCreate.getPath())); } diff --git a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java index a765c5a..840285f 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java @@ -13,7 +13,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; -import java.time.Instant; import java.util.List; import java.util.Set; import java.util.UUID; @@ -88,13 +87,11 @@ public class FlingServiceTest { flingEntity1.setId(UUID.randomUUID()); flingEntity1.setName("fling1"); flingEntity1.setAuthCode("testhash"); - flingEntity1.setCreationTime(Instant.now()); this.flingEntity2 = new FlingEntity(); flingEntity2.setId(UUID.randomUUID()); flingEntity2.setName("fling2"); flingEntity2.setShareId("shareId2"); - flingEntity2.setCreationTime(Instant.now()); when(flingRepository.save(any())).then(new Answer() { @Override diff --git a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java index e5554b7..0f6b5bb 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java @@ -18,8 +18,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; -import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -273,14 +271,12 @@ public class FileSystemArchiveTest { // Fling1/Artifact1 this.artifactEntity1 = new ArtifactEntity(); artifactEntity1.setId(UUID.randomUUID()); - artifactEntity1.setUploadTime(Instant.EPOCH); artifactEntity1.setPath(Path.of("artifact1")); artifactEntity1.setArchived(true); this.flingEntity1 = new FlingEntity(); flingEntity1.setId(new UUID(0, 0)); flingEntity1.setName("fling1"); - flingEntity1.setCreationTime(Instant.now()); artifactEntity1.setFling(flingEntity1); @@ -288,14 +284,12 @@ public class FileSystemArchiveTest { // Fling2/Artifact2 this.artifactEntity2 = new ArtifactEntity(); artifactEntity2.setId(UUID.randomUUID()); - artifactEntity2.setUploadTime(Instant.EPOCH.plus(12000, ChronoUnit.DAYS)); artifactEntity2.setPath(Path.of("/", "/sub", "artifact2")); artifactEntity2.setArchived(false); this.flingEntity2 = new FlingEntity(); flingEntity2.setId(new UUID(1, 0)); flingEntity2.setName("fling2"); - flingEntity2.setCreationTime(Instant.EPOCH); artifactEntity2.setFling(flingEntity2); } From 3679a9b501fffd1e17e052a4236030f33cee8ab8 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 01:31:25 +0200 Subject: [PATCH 09/30] Use valid semver and version update script --- .drone.yml | 9 ++++++--- VERSION | 1 + scripts/release.sh | 20 +++++++++++++++++++ service/fling/pom.xml | 2 +- .../src/main/resources/application-local.yml | 2 +- service/settings.xml | 9 --------- 6 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 VERSION create mode 100755 scripts/release.sh diff --git a/.drone.yml b/.drone.yml index 4be0d9c..d980a7d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -29,12 +29,15 @@ steps: from_secret: nexus_user NEXUS_PASSWORD: from_secret: nexus_password + VERSION: 0.1.0-snapshot commands: - ls -al - cd web/fling - npm install && npm run build - - tar czf fling-web-latest.tar.gz build/ - - curl --user "$NEXUS_USER:$NEXUS_PASSWORD" --upload-file ./fling-web-latest.tar.gz https://nexus.friedl.net/repository/build-artifacts/fling-web-latest.tar.gz + - tar czf fling-web-$VERSION.tar.gz build/ + - curl --user "$NEXUS_USER:$NEXUS_PASSWORD" + --upload-file ./fling-web-$VERSION.tar.gz + https://nexus.friedl.net/repository/build-artifacts/fling-web-$VERSION.tar.gz - name: publish image: plugins/docker @@ -46,7 +49,7 @@ steps: dockerfile: container/Dockerfile context: ./container repo: arminfriedl/fling - tags: dev + tags: 0.1.0-snapshot - name: runservice image: arminfriedl/fling:dev diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..7a29b0d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0-snapshot diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..913f133 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Go to project root +cd ${SCRIPT_DIR} +cd .. +echo "Working directory: $(pwd)" + +# Determine versions +CUR_VERSION=$(cat VERSION) +NEW_VERSION=${1} +echo "Replacing ${CUR_VERSION} with ${NEW_VERSION}" + +# Replace all versions +sed -i "s/${CUR_VERSION^^}/${NEW_VERSION^^}/g" service/fling/pom.xml +sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" service/fling/src/main/resources/*.yml +sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" .drone.yml +sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" VERSION diff --git a/service/fling/pom.xml b/service/fling/pom.xml index de6f6e8..52a63d1 100644 --- a/service/fling/pom.xml +++ b/service/fling/pom.xml @@ -11,7 +11,7 @@ net.friedl fling - 0.1-SNAPSHOT + 0.1.0-SNAPSHOT fling Simple artifact sharing diff --git a/service/fling/src/main/resources/application-local.yml b/service/fling/src/main/resources/application-local.yml index 84c1385..218b85c 100644 --- a/service/fling/src/main/resources/application-local.yml +++ b/service/fling/src/main/resources/application-local.yml @@ -33,6 +33,6 @@ fling: signing-key: "changeitchangeitchangeitchangeit" jwt-expiration: "180000" api: - version: "0" + version: "0.1.0-snapshot" server-url: "http://localhost:8080" server-description: "API server for dev" diff --git a/service/settings.xml b/service/settings.xml index 3fef3a2..ccdb209 100644 --- a/service/settings.xml +++ b/service/settings.xml @@ -10,13 +10,4 @@ ${env.NEXUS_PASSWORD} - - - - - nexus - central - https://nexus.friedl.net/repository/maven-central/ - - From 428390b0e20860a128364d40c97dcd297678c4ae Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 07:25:23 +0200 Subject: [PATCH 10/30] Build javascript client --- .drone.yml | 21 +++++++++++++++---- .../src/main/resources/application-prod.yml | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index d980a7d..eba2aa3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -52,15 +52,28 @@ steps: tags: 0.1.0-snapshot - name: runservice - image: arminfriedl/fling:dev + image: arminfriedl/fling:0.1.0-snapshot pull: always detach: true - name: generate-clients - image: openapitools/openapi-generator-cli + image: alpine commands: - - sleep 35 - - java -jar /opt/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i http://runservice:3000/v3/api-docs -g python -o flingclient + - apk add --update --no-cache openjdk11 npm + - sleep 20 + - npm install @openapitools/openapi-generator-cli -g + - openapi-generator generate + -i http://runservice:3000/v3/api-docs + -g python + -o flingclient.py + --enable-post-process-file + - openapi-generator generate + -i http://runservice:3000/v3/api-docs + -g javascript + --additional-properties projectName=flinclient,usePromises=true + -o flingclient.js + --enable-post-process-file + - cd flingclient.js && npm install && npm run build && cd .. volumes: - name: m2-cache diff --git a/service/fling/src/main/resources/application-prod.yml b/service/fling/src/main/resources/application-prod.yml index 9a19db4..d7bdb3e 100644 --- a/service/fling/src/main/resources/application-prod.yml +++ b/service/fling/src/main/resources/application-prod.yml @@ -28,6 +28,6 @@ fling: signing-key: "changeitchangeitchangeitchangeit" jwt-expiration: "180000" api: - version: "0" + version: "0.1.0-snapshot" server-url: "http://localhost:8080" server-description: "API server for dev" \ No newline at end of file From 525fd42b84ce9ba34802ced46c3b8864b263d481 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 11:02:57 +0200 Subject: [PATCH 11/30] Publish flingclient js package --- .drone.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index eba2aa3..0139f73 100644 --- a/.drone.yml +++ b/.drone.yml @@ -58,22 +58,33 @@ steps: - name: generate-clients image: alpine + environment: + NEXUS_USER: + from_secret: nexus_user + NEXUS_PASSWORD: + from_secret: nexus_password commands: - apk add --update --no-cache openjdk11 npm - sleep 20 - npm install @openapitools/openapi-generator-cli -g + # Python client - openapi-generator generate -i http://runservice:3000/v3/api-docs -g python -o flingclient.py --enable-post-process-file + # JavaScript client - openapi-generator generate -i http://runservice:3000/v3/api-docs -g javascript - --additional-properties projectName=flinclient,usePromises=true + --additional-properties projectName=flingclient,usePromises=true,npmRepository=https://nexus.friedl.net/repository/npm-private/ -o flingclient.js --enable-post-process-file - - cd flingclient.js && npm install && npm run build && cd .. + - cd flingclient.js && npm install && npm run build + - echo "https://nexus.friedl.net/repository/npm-private/" >> .npmrc + - echo -n "_auth=" >> .npmrc && echo -n "$NEXUS_USER:$NEXUS_PASSWORD" | base64 >> .npmrc + - echo "email=dev@friedl.net" >> .npmrc + - npm publish volumes: - name: m2-cache From dcd9b65bbbfe88b06acff88d33a6c6b31e55cfb0 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 11:46:47 +0200 Subject: [PATCH 12/30] Get correct versions for docker image --- .drone.yml | 2 ++ container/Dockerfile | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0139f73..e994973 100644 --- a/.drone.yml +++ b/.drone.yml @@ -50,6 +50,8 @@ steps: context: ./container repo: arminfriedl/fling tags: 0.1.0-snapshot + build_args: + - VERSION=0.1.0-snapshot - name: runservice image: arminfriedl/fling:0.1.0-snapshot diff --git a/container/Dockerfile b/container/Dockerfile index bdce3b3..08d47a2 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -1,10 +1,12 @@ FROM alpine:latest +ARG VERSION + RUN apk add --update --no-cache nginx openjdk11-jre && \ mkdir -p /var/fling/files && \ mkdir -p /tmp/fling && \ - wget -O /tmp/fling/service.jar "https://nexus.friedl.net/service/rest/v1/search/assets/download?sort=version&maven.groupId=net.friedl&maven.artifactId=fling&maven.baseVersion=*SNAPSHOT&maven.extension=jar" && \ - wget -O /tmp/fling/web.tar.gz "https://nexus.friedl.net/repository/build-artifacts/fling-web-latest.tar.gz" && \ + wget -O /tmp/fling/service.jar "https://nexus.friedl.net/service/rest/v1/search/assets/download?sort=version&maven.groupId=net.friedl&maven.artifactId=fling&maven.baseVersion=$(echo -n $VERSION | tr [:lower:] [:upper:])&maven.extension=jar" && \ + wget -O /tmp/fling/web.tar.gz "https://nexus.friedl.net/repository/build-artifacts/fling-web-$(echo -n $VERSION | tr [:upper:] [:lower:]).tar.gz" && \ tar xzf /tmp/fling/web.tar.gz -C /tmp/fling && \ ls -al /tmp/fling && \ mkdir -p /var/www/fling && mv /tmp/fling/build/* /var/www/fling && \ From f6fddae187487852952645233ca8f7fd990fa8fe Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 12:21:55 +0200 Subject: [PATCH 13/30] Run service from build --- .drone.yml | 16 +++++++++------- scripts/release.sh | 3 ++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.drone.yml b/.drone.yml index e994973..1ddbb16 100644 --- a/.drone.yml +++ b/.drone.yml @@ -19,6 +19,13 @@ steps: - cd service/fling - mvn -Pprod clean deploy +- name: runservice + image: adoptopenjdk:11-jre-hotspot + commands: + - cd service/fling/target + - java -jar fling-0.1.0-SNAPSHOT.jar + detach: true + - name: build-web image: node:latest volumes: @@ -53,11 +60,6 @@ steps: build_args: - VERSION=0.1.0-snapshot -- name: runservice - image: arminfriedl/fling:0.1.0-snapshot - pull: always - detach: true - - name: generate-clients image: alpine environment: @@ -71,13 +73,13 @@ steps: - npm install @openapitools/openapi-generator-cli -g # Python client - openapi-generator generate - -i http://runservice:3000/v3/api-docs + -i http://runservice:8080/v3/api-docs -g python -o flingclient.py --enable-post-process-file # JavaScript client - openapi-generator generate - -i http://runservice:3000/v3/api-docs + -i http://runservice:8080/v3/api-docs -g javascript --additional-properties projectName=flingclient,usePromises=true,npmRepository=https://nexus.friedl.net/repository/npm-private/ -o flingclient.js diff --git a/scripts/release.sh b/scripts/release.sh index 913f133..973c647 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -17,4 +17,5 @@ echo "Replacing ${CUR_VERSION} with ${NEW_VERSION}" sed -i "s/${CUR_VERSION^^}/${NEW_VERSION^^}/g" service/fling/pom.xml sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" service/fling/src/main/resources/*.yml sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" .drone.yml -sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" VERSION +sed -i "s/${CUR_VERSION^^}/${NEW_VERSION^^}/g" .drone.yml +sed -i "s/${CUR_VERSION}/${NEW_VERSION}/g" VERSION From 8e0685819d4d3576f7645f0873b2ddb902e2384b Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 12:51:11 +0200 Subject: [PATCH 14/30] Use scoped npm package This way the private nexus repository can be easily used besides the public repository, via a scoped repository in .npmrc --- .drone.yml | 2 +- scripts/release.sh | 1 + web/fling/.npmrc | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 web/fling/.npmrc diff --git a/.drone.yml b/.drone.yml index 1ddbb16..7634c87 100644 --- a/.drone.yml +++ b/.drone.yml @@ -81,7 +81,7 @@ steps: - openapi-generator generate -i http://runservice:8080/v3/api-docs -g javascript - --additional-properties projectName=flingclient,usePromises=true,npmRepository=https://nexus.friedl.net/repository/npm-private/ + --additional-properties projectName=@fling/flingclient,usePromises=true,npmRepository=https://nexus.friedl.net/repository/npm-private/ -o flingclient.js --enable-post-process-file - cd flingclient.js && npm install && npm run build diff --git a/scripts/release.sh b/scripts/release.sh index 973c647..7d21e78 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -18,4 +18,5 @@ sed -i "s/${CUR_VERSION^^}/${NEW_VERSION^^}/g" service/fling/pom.xml sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" service/fling/src/main/resources/*.yml sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" .drone.yml sed -i "s/${CUR_VERSION^^}/${NEW_VERSION^^}/g" .drone.yml +sed -i "s/${CUR_VERSION,,}/${NEW_VERSION,,}/g" web/fling/package.json sed -i "s/${CUR_VERSION}/${NEW_VERSION}/g" VERSION diff --git a/web/fling/.npmrc b/web/fling/.npmrc new file mode 100644 index 0000000..8f28521 --- /dev/null +++ b/web/fling/.npmrc @@ -0,0 +1 @@ +@fling:registry=https://nexus.friedl.net/repository/npm-private/ From 39fd416b4aa9e1986c607d396efc0a0fb24c136b Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 12 Jul 2020 18:48:12 +0200 Subject: [PATCH 15/30] Use generated client in web interface --- .drone.yml | 60 +-- .../net/friedl/fling/FlingConfiguration.java | 4 +- .../security/FlingWebSecurityConfigurer.java | 6 +- .../JwtAuthenticationFilter.java | 8 +- .../fling/service/AuthenticationService.java | 3 + .../fling/service/AuthorizationService.java | 2 + .../friedl/fling/service/FlingService.java | 2 + .../src/main/resources/application-local.yml | 4 +- .../service/AuthenticationServiceTest.java | 13 + .../service/AuthorizationServiceTest.java | 13 + .../fling/service/FlingServiceTest.java | 9 + web/fling/.env | 2 +- web/fling/.env.development.local | 2 +- web/fling/package-lock.json | 454 +++++++++--------- web/fling/package.json | 3 +- web/fling/src/components/admin/Login.jsx | 121 ++--- web/fling/src/util/fc.js | 25 + 17 files changed, 385 insertions(+), 346 deletions(-) create mode 100644 web/fling/src/util/fc.js diff --git a/.drone.yml b/.drone.yml index 7634c87..4989620 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,6 +26,36 @@ steps: - java -jar fling-0.1.0-SNAPSHOT.jar detach: true +- name: generate-clients + image: alpine + environment: + NEXUS_USER: + from_secret: nexus_user + NEXUS_PASSWORD: + from_secret: nexus_password + commands: + - apk add --update --no-cache openjdk11 npm + - sleep 20 + - npm install @openapitools/openapi-generator-cli -g + # Python client + - openapi-generator generate + -i http://runservice:8080/v3/api-docs + -g python + -o flingclient.py + --enable-post-process-file + # JavaScript client + - openapi-generator generate + -i http://runservice:8080/v3/api-docs + -g javascript + --additional-properties projectName=@fling/flingclient,usePromises=true,npmRepository=https://nexus.friedl.net/repository/npm-private/ + -o flingclient.js + --enable-post-process-file + - cd flingclient.js && npm install && npm run build + - echo "https://nexus.friedl.net/repository/npm-private/" >> .npmrc + - echo -n "_auth=" >> .npmrc && echo -n "$NEXUS_USER:$NEXUS_PASSWORD" | base64 >> .npmrc + - echo "email=dev@friedl.net" >> .npmrc + - npm publish + - name: build-web image: node:latest volumes: @@ -60,36 +90,6 @@ steps: build_args: - VERSION=0.1.0-snapshot -- name: generate-clients - image: alpine - environment: - NEXUS_USER: - from_secret: nexus_user - NEXUS_PASSWORD: - from_secret: nexus_password - commands: - - apk add --update --no-cache openjdk11 npm - - sleep 20 - - npm install @openapitools/openapi-generator-cli -g - # Python client - - openapi-generator generate - -i http://runservice:8080/v3/api-docs - -g python - -o flingclient.py - --enable-post-process-file - # JavaScript client - - openapi-generator generate - -i http://runservice:8080/v3/api-docs - -g javascript - --additional-properties projectName=@fling/flingclient,usePromises=true,npmRepository=https://nexus.friedl.net/repository/npm-private/ - -o flingclient.js - --enable-post-process-file - - cd flingclient.js && npm install && npm run build - - echo "https://nexus.friedl.net/repository/npm-private/" >> .npmrc - - echo -n "_auth=" >> .npmrc && echo -n "$NEXUS_USER:$NEXUS_PASSWORD" | base64 >> .npmrc - - echo "email=dev@friedl.net" >> .npmrc - - npm publish - volumes: - name: m2-cache host: diff --git a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java index 979d530..485d4e4 100644 --- a/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java +++ b/service/fling/src/main/java/net/friedl/fling/FlingConfiguration.java @@ -3,7 +3,7 @@ package net.friedl.fling; import java.nio.file.Path; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -19,7 +19,7 @@ public class FlingConfiguration { @Bean public PasswordEncoder passwordEncoder() { - return new Argon2PasswordEncoder(); + return new BCryptPasswordEncoder(); } @Bean diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index 5658bf1..7ff70ad 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -4,9 +4,8 @@ import static net.friedl.fling.security.FlingAuthorities.FLING_ADMIN; import static org.springframework.security.config.Customizer.withDefaults; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -24,12 +23,11 @@ import net.friedl.fling.security.authentication.JwtAuthenticationFilter; import net.friedl.fling.service.AuthorizationService; @Slf4j -@Configuration @EnableWebSecurity +@ConfigurationProperties(prefix = "fling.security") @Getter @Setter public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { - @Value("fling.security.allowedOrigins") private List allowedOrigins; private JwtAuthenticationFilter jwtAuthenticationFilter; diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java index 2ad3c5b..b4e5dc4 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java @@ -36,8 +36,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String header = request.getHeader(HEADER_STRING); if (header == null || !header.startsWith(TOKEN_PREFIX)) { - log.info("Anonymous request for {} {}?{}", request.getMethod(), request.getRequestURL(), - request.getQueryString()); + log.info("Anonymous request for {} {}{}", request.getMethod(), request.getRequestURL(), + request.getQueryString() != null ? "?"+request.getQueryString(): ""); filterChain.doFilter(request, response); return; } @@ -47,8 +47,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { SecurityContext securityContext = SecurityContextHolder.getContext(); if (securityContext.getAuthentication() == null) { - log.info("Authenticating request for {} {}?{}", request.getMethod(), request.getRequestURL(), - request.getQueryString()); + log.info("Authenticating request for {} {}{}", request.getMethod(), request.getRequestURL(), + request.getQueryString() != null ? "?"+request.getQueryString(): ""); FlingToken token = authenticationService.parseAuthentication(authToken); log.info("Authenticated as {}", token.getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.joining(","))); diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java index afe15f1..71038aa 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java @@ -5,6 +5,7 @@ import java.time.Instant; import java.util.Date; import java.util.Optional; import java.util.UUID; +import javax.persistence.EntityNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.BadCredentialsException; @@ -70,6 +71,8 @@ public class AuthenticationService { public Optional authenticate(UserAuthDto userAuth) { log.info("Authenticating for fling [.shareId={}]", userAuth.getShareId()); FlingEntity flingEntity = flingRepository.findByShareId(userAuth.getShareId()); + if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+userAuth.getShareId()); } + String providedAuthCodeHash = passwordEncoder.encode(userAuth.getAuthCode()); String actualAuthCodeHash = flingEntity.getAuthCode(); diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java index d1a151d..a405cfe 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java @@ -1,6 +1,7 @@ package net.friedl.fling.service; import java.util.UUID; +import javax.persistence.EntityNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.stereotype.Service; @@ -69,6 +70,7 @@ public class AuthorizationService { public boolean allowFlingAccessByShareId(String shareId, AbstractAuthenticationToken token) { FlingEntity flingEntity = flingRepository.findByShareId(shareId); + if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+shareId); } return allowFlingAccess(flingEntity.getId(), token); } diff --git a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java index 2bf418c..ecfb474 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java @@ -5,6 +5,7 @@ import java.util.Base64; import java.util.List; import java.util.Set; import java.util.UUID; +import javax.persistence.EntityNotFoundException; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -89,6 +90,7 @@ public class FlingService { public FlingDto getByShareId(String shareId) { FlingEntity flingEntity = flingRepository.findByShareId(shareId); + if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+shareId); } return flingMapper.map(flingEntity); } diff --git a/service/fling/src/main/resources/application-local.yml b/service/fling/src/main/resources/application-local.yml index 218b85c..43108ed 100644 --- a/service/fling/src/main/resources/application-local.yml +++ b/service/fling/src/main/resources/application-local.yml @@ -28,8 +28,8 @@ fling: - "http://localhost:3000" - "http://localhost:5000" - "http://10.0.2.2:5000" - admin-name: "adminName" - admin-password: "adminPassword" + admin-name: "admin" + admin-password: "123" signing-key: "changeitchangeitchangeitchangeit" jwt-expiration: "180000" api: diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java index 14181fa..658ef44 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java @@ -11,6 +11,7 @@ import java.security.Key; import java.util.Map; import java.util.Optional; import java.util.UUID; +import javax.persistence.EntityNotFoundException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -115,6 +116,18 @@ public class AuthenticationServiceTest { assertThat(authenticationService.authenticate(userAuthDto), not(equalTo(Optional.empty()))); } + @Test + public void authenticate_noFlingForShareId_throws() { + UserAuthDto userAuthDto = UserAuthDto.builder() + .authCode("authCode") + .shareId("doesNotExist").build(); + + when(flingRepository.findByShareId(any(String.class))).thenReturn(null); + when(passwordEncoder.encode(any(String.class))).thenReturn("authCodeHash"); + + assertThrows(EntityNotFoundException.class, () -> authenticationService.authenticate(userAuthDto)); + } + @Test public void parseAuthentication_owner_AdminAuthority() { Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java index 5540941..3ad1820 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java @@ -1,10 +1,13 @@ package net.friedl.fling.service; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import java.util.List; import java.util.UUID; +import javax.persistence.EntityNotFoundException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -113,4 +116,14 @@ public class AuthorizationServiceTest { FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); assertTrue(authorizationService.allowFlingAccess(new UUID(0, 0), flingToken)); } + + @Test + public void allowFlingAccessByShareId_noFlingForShareId_throw() { + FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + when(flingRepository.findByShareId(any(String.class))).thenReturn(null); + + assertThrows(EntityNotFoundException.class, + () -> authorizationService.allowFlingAccessByShareId("doesNotExist", flingToken)); + } + } diff --git a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java index 840285f..f16072b 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/FlingServiceTest.java @@ -9,6 +9,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -17,6 +18,7 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import javax.persistence.EntityNotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -161,6 +163,13 @@ public class FlingServiceTest { assertThat(foundFling.getShareId(), equalTo("shareId2")); } + @Test + public void getByShareId_noFlingForShareId_throws() { + when(flingRepository.findByShareId(any(String.class))).thenReturn(null); + + assertThrows(EntityNotFoundException.class, () -> flingService.getByShareId("doesNotExist")); + } + @Test public void delete_deletesFromArchiveAndDb() throws IOException { UUID testId = UUID.randomUUID(); diff --git a/web/fling/.env b/web/fling/.env index 1ffa947..e605af4 100644 --- a/web/fling/.env +++ b/web/fling/.env @@ -1,2 +1,2 @@ -REACT_APP_API=https://fling.friedl.net/api +REACT_APP_API=https://fling.friedl.net REACT_APP_LOGLEVEL=warn diff --git a/web/fling/.env.development.local b/web/fling/.env.development.local index 445b133..ecf9b5b 100644 --- a/web/fling/.env.development.local +++ b/web/fling/.env.development.local @@ -1,2 +1,2 @@ -REACT_APP_API=http://localhost:8080/api +REACT_APP_API=http://localhost:8080/ REACT_APP_LOGLEVEL=trace diff --git a/web/fling/package-lock.json b/web/fling/package-lock.json index d495c51..c595a82 100644 --- a/web/fling/package-lock.json +++ b/web/fling/package-lock.json @@ -4,6 +4,135 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/cli": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.10.4.tgz", + "integrity": "sha512-xX99K4V1BzGJdQANK5cwK+EpF1vP9gvqhn+iWvG+TubCjecplW7RSQimJ2jcCvu6fnK5pY6mZMdu6EWTj32QVA==", + "requires": { + "chokidar": "^2.1.8", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.13", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "optional": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -1078,6 +1207,15 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@fling/flingclient": { + "version": "0.1.0-snapshot", + "resolved": "https://nexus.friedl.net/repository/npm-private/@fling/flingclient/-/flingclient-0.1.0-snapshot.tgz", + "integrity": "sha512-KXeJE/tTCi+IRBZ8pBeFLFEn7GDBWw/aIDj4xaofjw6S0DFEpw5TwW+Oh45NALk/SEiR4DKBuG/sfgiHrpZwLA==", + "requires": { + "@babel/cli": "^7.0.0", + "superagent": "3.7.0" + } + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -3808,6 +3946,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -5838,6 +5981,11 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -5907,6 +6055,11 @@ "minipass": "^3.0.0" } }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -6154,12 +6307,12 @@ } }, "globule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", - "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", "requires": { "glob": "~7.1.1", - "lodash": "~4.17.12", + "lodash": "~4.17.10", "minimatch": "~3.0.2" } }, @@ -6512,9 +6665,9 @@ "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -8132,9 +8285,9 @@ } }, "js-base64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", - "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.3.tgz", + "integrity": "sha512-fiUvdfCaAXoQTHdKMgTvg6IkecXDcVz6V5rlftUTclF9IKBjMizvSdQaCl/z/6TApDeby5NL+axYou3i0mu1Pg==" }, "js-tokens": { "version": "4.0.0", @@ -8448,9 +8601,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -9270,9 +9423,9 @@ "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==" }, "node-sass": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.0.tgz", - "integrity": "sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -9288,7 +9441,7 @@ "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", + "sass-graph": "2.2.5", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -12229,219 +12382,14 @@ "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==" }, "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", "requires": { "glob": "^7.0.0", "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "requires": { - "camelcase": "^3.0.0" - } - } + "yargs": "^13.3.2" } }, "sass-loader": { @@ -13480,6 +13428,60 @@ } } }, + "superagent": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.7.0.tgz", + "integrity": "sha512-/8trxO6NbLx4YXb7IeeFTSmsQ35pQBiTBsLNvobZx7qBzBeHYvKCyIIhW2gNcWbLzYxPAjdgFbiepd8ypwC0Gw==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/web/fling/package.json b/web/fling/package.json index 20e6ba4..1d53417 100644 --- a/web/fling/package.json +++ b/web/fling/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fling/flingclient": "0.1.0-snapshot", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", @@ -11,7 +12,7 @@ "core-js": "^3.6.5", "jwt-decode": "^2.2.0", "loglevel": "^1.6.8", - "node-sass": "^4.14.0", + "node-sass": "^4.14.1", "normalize.css": "^8.0.1", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/web/fling/src/components/admin/Login.jsx b/web/fling/src/components/admin/Login.jsx index 361dc46..b56f599 100644 --- a/web/fling/src/components/admin/Login.jsx +++ b/web/fling/src/components/admin/Login.jsx @@ -1,88 +1,59 @@ import log from 'loglevel'; -import React, {useState, useEffect} from 'react'; +import React, {useState} from 'react'; import {useHistory, useLocation} from 'react-router-dom'; -import request, {setAuth} from '../../util/request'; - -import Error from './Error'; +import {fc, AuthClient} from '../../util/fc'; export default function Login() { - const [errors, setErrors] = useState([]); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const history = useHistory(); - const location = useLocation(); - const { from } = location.state || { from: { pathname: "/admin" } }; + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const history = useHistory(); + const location = useLocation(); + const { from } = location.state || { from: { pathname: "/admin" } }; - useEffect(() => setAuth(null), []); - - return ( -

-
- -
-
- - -
-
- - -
-
-
- -
- -
- -
- -

Ready. Set. Fling.

+ return ( +
+
+
+
+ + setUsername(ev.currentTarget.value)} />
+
+ + setPassword(ev.currentTarget.value)} /> +
+
+
+ +
+ +
+ -
- ); +

Ready. Set. Fling.

+
- function handleSubmit(ev) { - ev.preventDefault(); +
+ ); - request.post("/auth/owner", {'username': username, 'password': password}) - .then(response => { - log.info("Logged in successfully"); - setAuth(response.data); - history.replace(from); - }) - .catch(error => { - log.error(error); - let response = error.response; - response.data && response.data.message && setErrors( prev => [response.data.message, ...prev] ); - }); - }; + function handleSubmit(ev) { + ev.preventDefault(); - function handleChange(ev) { - let name = ev.target.name; - let val = ev.target.value; + let authClient = new AuthClient(); + let opt = {adminAuth: new fc.AdminAuth(username, password)}; - switch(name) { - case "username": - setUsername(val); - break; - case "password": - setPassword(val); - break; - default: - log.error(`Cannot handle change ${name}`); - break; - } - }; - - function clearErrors() { - setErrors([]); - } + authClient.authenticateOwner(opt) + .then(response => { + log.info("Login successful"); + sessionStorage.setItem['token'] = response; + log.info("Returning back to", from); + history.replace(from); + }).catch(log.error); + }; } diff --git a/web/fling/src/util/fc.js b/web/fling/src/util/fc.js new file mode 100644 index 0000000..b901f7a --- /dev/null +++ b/web/fling/src/util/fc.js @@ -0,0 +1,25 @@ +import * as fc from '@fling/flingclient'; + +let clientConfig = (token) => { + let config = new fc.ApiClient(); + config.basePath = process.env.REACT_APP_API.replace(/\/+$/, ''); + + token = token || sessionStorage.getItem['token']; + if(token) { config.authentications['bearer'].accessToken = token; } + + return config; +}; + +function FlingClient(token) { + return new fc.FlingApi(clientConfig(token)); +} + +function ArtifactClient(token) { + return new fc.ArtifactApi(clientConfig(token)); +} + +function AuthClient() { + return new fc.AuthApi(clientConfig()); +} + +export {FlingClient, ArtifactClient, AuthClient, fc}; From bcfbf349cde81bb6a100bf2e5e6b08780630da9f Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 13 Jul 2020 16:20:13 +0200 Subject: [PATCH 16/30] Add security scheme to controller for client generation Clients (at least javascript client) generated from the OpenAPI spec do add the bearer token to the request without specifying the bearer requirement. --- .../fling/controller/ArtifactController.java | 2 + .../fling/controller/FlingController.java | 2 + web/fling/src/App.jsx | 91 +++++++++++-------- web/fling/src/components/admin/FlingList.jsx | 45 +++++---- web/fling/src/components/admin/Login.jsx | 4 +- web/fling/src/util/fc.js | 2 +- web/fling/src/util/jwt.js | 26 ++++++ 7 files changed, 105 insertions(+), 67 deletions(-) create mode 100644 web/fling/src/util/jwt.js diff --git a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java index 7dcde8e..e6e126f 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.service.ArtifactService; @@ -28,6 +29,7 @@ import net.friedl.fling.service.archive.ArchiveService; @RestController @RequestMapping("/api/artifacts") @Tag(name = "artifact", description = "Operations on /api/artifacts") +@SecurityRequirement(name = "bearer") @Validated public class ArtifactController { diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index ba9a11c..3037aa2 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.FlingDto; @@ -32,6 +33,7 @@ import net.friedl.fling.service.archive.ArchiveService; @RestController @RequestMapping("/api/fling") @Tag(name = "fling", description = "Operations on /api/fling") +@SecurityRequirement(name = "bearer") public class FlingController { private FlingService flingService; diff --git a/web/fling/src/App.jsx b/web/fling/src/App.jsx index d3dcb83..5cfcb31 100644 --- a/web/fling/src/App.jsx +++ b/web/fling/src/App.jsx @@ -3,7 +3,7 @@ import React from 'react'; import {Switch, Route, Redirect} from "react-router-dom"; -import request, {isOwner, isUser} from './util/request'; +import jwt from './util/jwt.js'; import Login from './components/admin/Login'; import FlingAdmin from './components/admin/FlingAdmin'; @@ -13,53 +13,64 @@ import FlingUser from './components/user/FlingUser'; import LandingPage from './components/LandingPage'; export default () => { - return ( - - + return ( + + - - - + + + - - + + - Not implemented - - ); + Not implemented + + ); } -// A wrapper for that redirects to the login -// screen if you're not yet authenticated. +/* + * A wrapper for that redirects to the login screen if no admin + * authentication token was found. + * + * Note that the token check is purely client-side. It provides no actual + * protection! It is hence possible to reach the admin site with some small + * amount of trickery. Without a valid token no meaningful actions are possible + * on the admin page though. + */ function OwnerRoute({ children, ...rest }) { - return ( - { - log.info(request.defaults); - if(isOwner()) { return children; } - else { return ; } - }} - /> - ); + log.info(`Routing request for ${rest['path']}`); + return ( + { + if (jwt.hasSubject("admin")) { return children; } + else { return ; } + }} + /> + ); } -// A wrapper for that redirects to the unlock -// screen if the fling is protected +/* A wrapper for that redirects to the unlock screen if no authorized token + * was found. + * + * Note that the token check is purely client-side. It provides no actual + * protection! It is hence possible to reach the target site with some small + * amount of trickery. Without a valid token, no meaningful actions are possible + * on the target page though - this must be checked server side. + */ function UserRoute({ children, ...rest }) { - return ( - { - log.info(request.defaults); - log.info(match); - log.info(location); - let x = {from: location, shareId: match.params.shareId}; + log.debug(`Routing request for ${rest['path']}`); + return ( + { + let state = {from: location, shareId: match.params.shareId}; + let authorized = jwt.hasSubject("admin") || (jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId'])); - if(isOwner()) { return children; } - else if(isUser(match.params.shareId)) { return children; } - else { return ; } - }} - /> - ); + if (authorized) { return children; } + else { return ; } + }} + /> + ); } diff --git a/web/fling/src/components/admin/FlingList.jsx b/web/fling/src/components/admin/FlingList.jsx index 3b501a6..99da35d 100644 --- a/web/fling/src/components/admin/FlingList.jsx +++ b/web/fling/src/components/admin/FlingList.jsx @@ -1,37 +1,34 @@ import log from 'loglevel'; import React, {useState, useEffect} from 'react'; -import {flingClient} from '../../util/flingclient'; +import {FlingClient} from '../../util/fc'; import FlingTile from './FlingTile'; export default function FlingList(props) { - const [flings, setFlings] = useState([]); - useEffect(() => { (async () => { - let flings = await flingClient.getFlings(); + const [flings, setFlings] = useState([]); + useEffect(() => { + let flingClient = new FlingClient(); + flingClient.getFlings() + .then(flings => { let newFlings = []; - for (let fling of flings) { - let flingTile = ; - newFlings.push(flingTile); + let flingTile = ; + newFlings.push(flingTile); } setFlings(newFlings); - })(); } , []); + }).catch(log.error); + }, []); - return( -
- {log.info(`Got active fling: ${props.activeFling}`)} -
-
My Flings
-
-
- {flings} -
-
- ); - - async function refreshFlingList() { - } + return( +
+ {log.info(`Got active fling: ${props.activeFling}`)} +
+
My Flings
+
+
+ {flings} +
+
+ ); } diff --git a/web/fling/src/components/admin/Login.jsx b/web/fling/src/components/admin/Login.jsx index b56f599..3599e22 100644 --- a/web/fling/src/components/admin/Login.jsx +++ b/web/fling/src/components/admin/Login.jsx @@ -51,8 +51,8 @@ export default function Login() { authClient.authenticateOwner(opt) .then(response => { log.info("Login successful"); - sessionStorage.setItem['token'] = response; - log.info("Returning back to", from); + sessionStorage.setItem('token', response); + log.debug("Returning back to", from); history.replace(from); }).catch(log.error); }; diff --git a/web/fling/src/util/fc.js b/web/fling/src/util/fc.js index b901f7a..11445d7 100644 --- a/web/fling/src/util/fc.js +++ b/web/fling/src/util/fc.js @@ -4,7 +4,7 @@ let clientConfig = (token) => { let config = new fc.ApiClient(); config.basePath = process.env.REACT_APP_API.replace(/\/+$/, ''); - token = token || sessionStorage.getItem['token']; + token = token || sessionStorage.getItem('token'); if(token) { config.authentications['bearer'].accessToken = token; } return config; diff --git a/web/fling/src/util/jwt.js b/web/fling/src/util/jwt.js new file mode 100644 index 0000000..696d13e --- /dev/null +++ b/web/fling/src/util/jwt.js @@ -0,0 +1,26 @@ +/** + * Utilities for working with JWT tokens + */ +import jwtDecode from 'jwt-decode'; + +let jwt = { + /** + * Check the session store token for an arbitrary claim + */ + hasClaim: function (name, value) { + if(!sessionStorage.getItem('token')) return false; + let tokenPayload = jwtDecode(sessionStorage.getItem('token')); + return tokenPayload[name] === value; + }, + + /** + * Check the session store token for a subject + */ + hasSubject: function (value) { + if(!sessionStorage.getItem('token')) return false; + let tokenPayload = jwtDecode(sessionStorage.getItem('token')); + return tokenPayload['sub'] === value; + } +}; + +export default jwt; From 0c1fe8efcedfd9f2ab2732f0764cc64858aa196e Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 13 Jul 2020 21:55:02 +0200 Subject: [PATCH 17/30] Remove invalid constraints, prepare python generator for deployment --- .drone.yml | 14 ++++++++++++++ .../net/friedl/fling/model/dto/ArtifactDto.java | 1 - .../java/net/friedl/fling/model/dto/FlingDto.java | 2 -- .../net/friedl/fling/model/ArtifactDtoTest.java | 7 ++----- .../java/net/friedl/fling/model/FlingDtoTest.java | 14 ++++---------- web/fling/package-lock.json | 2 +- web/fling/src/components/admin/FlingTile.jsx | 8 ++++---- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4989620..87e3192 100644 --- a/.drone.yml +++ b/.drone.yml @@ -41,8 +41,22 @@ steps: - openapi-generator generate -i http://runservice:8080/v3/api-docs -g python + --additional-properties packageName=flingclient -o flingclient.py --enable-post-process-file + - cd flingclient.py + - |+ + cat << EOF + [distutils] + index-servers = + nexus + [private-repository] + repository = + username = + password = + EOF >> .pypirc + - cat .pypirc + - cd .. # JavaScript client - openapi-generator generate -i http://runservice:8080/v3/api-docs diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java index 8dfcb90..e02b983 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/ArtifactDto.java @@ -18,7 +18,6 @@ import lombok.NoArgsConstructor; @Schema(name = "Artifact") public class ArtifactDto { @Schema(accessMode = AccessMode.READ_ONLY, type = "string") - @NotNull private UUID id; @Schema(type = "string", diff --git a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java index be6379d..ff2ab54 100644 --- a/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java +++ b/service/fling/src/main/java/net/friedl/fling/model/dto/FlingDto.java @@ -17,7 +17,6 @@ import lombok.NoArgsConstructor; @Schema(name = "Fling") public class FlingDto { @Schema(accessMode = AccessMode.READ_ONLY, type = "string") - @NotNull private UUID id; @Schema(description = "Name of the fling") @@ -26,7 +25,6 @@ public class FlingDto { @Schema(type = "integer", format = "int64", accessMode = AccessMode.READ_ONLY, description = "Creation time in milliseconds since the unix epoch 01.01.1970 00:00:00 UTC") - @NotNull private Instant creationTime; @Schema(description = "Share id of the fling. Used in the share link.") diff --git a/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java b/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java index 394079d..3e10a39 100644 --- a/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java +++ b/service/fling/src/test/java/net/friedl/fling/model/ArtifactDtoTest.java @@ -22,7 +22,7 @@ public class ArtifactDtoTest { private Validator validator; @Test - void testSetId_null_validationFails() { + void testSetId_null_validationOk() { ArtifactDto artifactDto = ArtifactDto.builder() .id(null) .path(Paths.get("test")) @@ -31,10 +31,7 @@ public class ArtifactDtoTest { Set> constraintViolations = validator.validate(artifactDto); - assertThat(constraintViolations).hasSize(1); - ConstraintViolation violation = constraintViolations.iterator().next(); - assertThat(violation.getPropertyPath().toString()).isEqualTo("id"); - assertThat(violation.getMessage()).isEqualTo("must not be null"); + assertThat(constraintViolations).hasSize(0); } @Test diff --git a/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java b/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java index 6d6f2f5..6960013 100644 --- a/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java +++ b/service/fling/src/test/java/net/friedl/fling/model/FlingDtoTest.java @@ -22,7 +22,7 @@ public class FlingDtoTest { private Validator validator; @Test - void testSetId_null_validationFails() { + void testSetId_null_validationOk() { FlingDto flingDto = FlingDto.builder() .id(null) .name("test") @@ -33,10 +33,7 @@ public class FlingDtoTest { Set> constraintViolations = validator.validate(flingDto); - assertThat(constraintViolations).hasSize(1); - ConstraintViolation violation = constraintViolations.iterator().next(); - assertThat(violation.getPropertyPath().toString()).isEqualTo("id"); - assertThat(violation.getMessage()).isEqualTo("must not be null"); + assertThat(constraintViolations).hasSize(0); } @Test @@ -58,7 +55,7 @@ public class FlingDtoTest { } @Test - void testSetCreationTime_null_validationFails() { + void testSetCreationTime_null_validationOk() { FlingDto flingDto = FlingDto.builder() .id(new UUID(0L, 0L)) .name("test") @@ -69,10 +66,7 @@ public class FlingDtoTest { Set> constraintViolations = validator.validate(flingDto); - assertThat(constraintViolations).hasSize(1); - ConstraintViolation violation = constraintViolations.iterator().next(); - assertThat(violation.getPropertyPath().toString()).isEqualTo("creationTime"); - assertThat(violation.getMessage()).isEqualTo("must not be null"); + assertThat(constraintViolations).hasSize(0); } diff --git a/web/fling/package-lock.json b/web/fling/package-lock.json index c595a82..554398d 100644 --- a/web/fling/package-lock.json +++ b/web/fling/package-lock.json @@ -1210,7 +1210,7 @@ "@fling/flingclient": { "version": "0.1.0-snapshot", "resolved": "https://nexus.friedl.net/repository/npm-private/@fling/flingclient/-/flingclient-0.1.0-snapshot.tgz", - "integrity": "sha512-KXeJE/tTCi+IRBZ8pBeFLFEn7GDBWw/aIDj4xaofjw6S0DFEpw5TwW+Oh45NALk/SEiR4DKBuG/sfgiHrpZwLA==", + "integrity": "sha512-Ws4M0st41sb8gQz07k3ygEXoqx5GtSmH9rr/RzrJhQELt0dVxNi8qJY3VcJxV6svpYvwKDacRyy+aXnNnOG6/w==", "requires": { "@babel/cli": "^7.0.0", "superagent": "3.7.0" diff --git a/web/fling/src/components/admin/FlingTile.jsx b/web/fling/src/components/admin/FlingTile.jsx index 8b9b7f0..fd5f00f 100644 --- a/web/fling/src/components/admin/FlingTile.jsx +++ b/web/fling/src/components/admin/FlingTile.jsx @@ -16,16 +16,16 @@ function TileAction(props) {
  • - +
  • @@ -57,7 +57,7 @@ function TileAction(props) { } async function toggleShared() { - await flingClient.putFling(props.fling.id, {"sharing": {"shared": !props.fling.sharing.shared}}); + await flingClient.putFling(props.fling.id, {"sharing": {"shared": !props.fling.shared}}); await props.refreshFlingListFn(); } } From 41f2a22f3d48c065629c6856b45ee80384526ef4 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 19 Jul 2020 16:12:59 +0200 Subject: [PATCH 18/30] Introduce redux store --- examples/python/fling.py | 32 ++++ examples/querysheet.http | 39 +++++ web/fling/package-lock.json | 16 +- web/fling/package.json | 2 + web/fling/src/App.jsx | 37 ++++- web/fling/src/components/LandingPage.jsx | 1 - web/fling/src/components/admin/Error.jsx | 23 --- web/fling/src/components/admin/FlingAdmin.jsx | 43 ++++-- web/fling/src/components/admin/FlingList.jsx | 27 +--- web/fling/src/components/admin/FlingTile.jsx | 140 +++++++++--------- web/fling/src/index.js | 31 ++-- web/fling/src/redux/actionTypes.js | 3 + web/fling/src/redux/actions.js | 67 +++++++++ web/fling/src/redux/reducer.js | 3 - web/fling/src/redux/reducers/flings.js | 29 ++++ web/fling/src/redux/reducers/index.js | 4 + web/fling/src/redux/selectorTypes.js | 3 + web/fling/src/redux/selectors.js | 0 .../src/redux/selectors/flingSelectors.js | 10 ++ web/fling/src/redux/state.js | 4 - web/fling/src/util/fc.js | 7 + 21 files changed, 365 insertions(+), 156 deletions(-) create mode 100644 examples/python/fling.py create mode 100644 examples/querysheet.http delete mode 100644 web/fling/src/components/admin/Error.jsx create mode 100644 web/fling/src/redux/actionTypes.js delete mode 100644 web/fling/src/redux/reducer.js create mode 100644 web/fling/src/redux/reducers/flings.js create mode 100644 web/fling/src/redux/reducers/index.js create mode 100644 web/fling/src/redux/selectorTypes.js create mode 100644 web/fling/src/redux/selectors.js create mode 100644 web/fling/src/redux/selectors/flingSelectors.js delete mode 100644 web/fling/src/redux/state.js diff --git a/examples/python/fling.py b/examples/python/fling.py new file mode 100644 index 0000000..dc0d2dd --- /dev/null +++ b/examples/python/fling.py @@ -0,0 +1,32 @@ +import flingclient as fc +from flingclient.rest import ApiException +from datetime import datetime + +# Per default the dockerized fling service runs on localhost:3000 In case you +# run your own instance, change the base url +configuration = fc.Configuration(host="http://localhost:3000") + +# Every call, with the exception of `/api/auth`, is has to be authorized by a +# bearer token. Get a token by authenticating as admin and set it into the +# configuration. All subsequent calls will send this token in the header as +# `Authorization: Bearer header` +def authenticate(admin_user, admin_password): + with fc.ApiClient(configuration) as api_client: + auth_client = fc.AuthApi(api_client) + admin_auth = fc.AdminAuth(admin_user, admin_password) + configuration.access_token = auth_client.authenticate_owner(admin_auth=admin_auth) + +admin_user = input("Username: ") +admin_password = input("Password: ") +authenticate(admin_user, admin_password) + +with fc.ApiClient(configuration) as api_client: + # Create a new fling + fling_client = fc.FlingApi(api_client) + fling = fc.Fling(name="A Fling from Python", auth_code="secret", + direct_download=False, allow_upload=True, + expiration_time=datetime(2099, 12, 12)) + fling = fling_client.post_fling() + print(f"Created a new fling: {fling}") + + # diff --git a/examples/querysheet.http b/examples/querysheet.http new file mode 100644 index 0000000..a7b16a6 --- /dev/null +++ b/examples/querysheet.http @@ -0,0 +1,39 @@ +###################################### +# Fling Querysheet for restclient.el # +###################################### + +# Authenticate as user +POST http://localhost:8080/api/auth/user +{"shareId": "shareId", "authCode":"secret"} +-> jq-set-var :token . + +# :token = Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTQ0NjEzNzMsImV4cCI6MTU5NDY0MTM3Mywic3ViIjoiYWRtaW4ifQ.yu6sF1aE6sW4Jx1hBMj6iUsy8xfiaRGlIFVnHK4YkU8 + +# Authenticate as admin +POST http://localhost:8080/api/auth/admin +Content-Type: application/json +{"adminName": "admin", "adminPassword":"123"} +-> run-hook (restclient-set-var ":token" (buffer-substring-no-properties 1 (line-end-position))) + +# Get all flings +GET http://localhost:8080/api/fling +Authorization: Bearer :token + +# +:flingId = dfc208a3-5924-43b4-aa6a-c263541dca5e + +# Get one fling +GET http://localhost:8080/api/fling/:flingId +:token + +# Get all artifacts +GET http://localhost:8080/api/fling/:flingId/artifacts +:token + +# +GET https://httpbin.org/json +-> jq-set-var :my-var .slideshow.slides[0].title + +# +GET http://httpbin.org/ip +-> run-hook (restclient-set-var ":my-ip" (cdr (assq 'origin (json-read)))) diff --git a/web/fling/package-lock.json b/web/fling/package-lock.json index 554398d..0404fa6 100644 --- a/web/fling/package-lock.json +++ b/web/fling/package-lock.json @@ -6740,9 +6740,9 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, "immer": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", - "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.5.tgz", + "integrity": "sha512-TtRAKZyuqld2eYjvWgXISLJ0ZlOl1OOTzRmrmiY8SlB0dnAhZ1OiykIDL5KDFNaPHDXiLfGQFNJGtet8z8AEmg==" }, "import-cwd": { "version": "2.1.0", @@ -11512,6 +11512,11 @@ } } }, + "immer": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", + "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" + }, "inquirer": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", @@ -11922,6 +11927,11 @@ "symbol-observable": "^1.0.2" } }, + "redux-thunk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", + "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", diff --git a/web/fling/package.json b/web/fling/package.json index 1d53417..4babb47 100644 --- a/web/fling/package.json +++ b/web/fling/package.json @@ -10,6 +10,7 @@ "axios": "^0.19.2", "classnames": "^2.2.6", "core-js": "^3.6.5", + "immer": "^7.0.5", "jwt-decode": "^2.2.0", "loglevel": "^1.6.8", "node-sass": "^4.14.1", @@ -20,6 +21,7 @@ "react-router-dom": "^5.1.2", "react-scripts": "3.4.1", "redux": "^4.0.5", + "redux-thunk": "^2.3.0", "spectre.css": "^0.5.8" }, "scripts": { diff --git a/web/fling/src/App.jsx b/web/fling/src/App.jsx index 5cfcb31..4c9f970 100644 --- a/web/fling/src/App.jsx +++ b/web/fling/src/App.jsx @@ -1,7 +1,7 @@ import log from 'loglevel'; import React from 'react'; -import {Switch, Route, Redirect} from "react-router-dom"; +import { Switch, Route, Redirect } from "react-router-dom"; import jwt from './util/jwt.js'; @@ -12,6 +12,19 @@ import Unlock from './components/user/Unlock'; import FlingUser from './components/user/FlingUser'; import LandingPage from './components/LandingPage'; +/** + * Front routes, defaults to a 404 Page. + * Routes: + * - / : Landing page + * - /admin/login : A login page. Redirects with admin token upon successful + login + * - /admin : The fling administration page. Redirects to a login page if not + authenticated + * - /admin/[fling id]/* : Go directly to a fling (sub-)page. Redirects to a + login page if not authenticated + * - /unlock : A unlock page. Redirects with user token upon successful login. + * - /f/[shareId] : Opens a fling page for a user + */ export default () => { return ( @@ -19,7 +32,7 @@ export default () => { - + @@ -45,14 +58,19 @@ function OwnerRoute({ children, ...rest }) { {...rest} render={({ location }) => { if (jwt.hasSubject("admin")) { return children; } - else { return ; } + else { + return ; + } }} /> ); } -/* A wrapper for that redirects to the unlock screen if no authorized token - * was found. +/* A wrapper for that redirects to the unlock screen if no authorized + * token * was found. * * Note that the token check is purely client-side. It provides no actual * protection! It is hence possible to reach the target site with some small @@ -65,11 +83,14 @@ function UserRoute({ children, ...rest }) { { - let state = {from: location, shareId: match.params.shareId}; - let authorized = jwt.hasSubject("admin") || (jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId'])); + let state = { from: location, shareId: match.params.shareId }; + + let authorized = + jwt.hasSubject("admin") + || ( jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId']) ); if (authorized) { return children; } - else { return ; } + else { return ; } }} /> ); diff --git a/web/fling/src/components/LandingPage.jsx b/web/fling/src/components/LandingPage.jsx index 1d48787..ac5995d 100644 --- a/web/fling/src/components/LandingPage.jsx +++ b/web/fling/src/components/LandingPage.jsx @@ -13,7 +13,6 @@ export default function LandingPage() { function openFling(ev) { ev.preventDefault(); - window.location = `/f/${shareId}`; } diff --git a/web/fling/src/components/admin/Error.jsx b/web/fling/src/components/admin/Error.jsx deleted file mode 100644 index 25e07b9..0000000 --- a/web/fling/src/components/admin/Error.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -export default (props) => { - function renderError() { - return ( -
    - -
    Ooops!
    -
  • - { props.errors.map( (err, idx) =>
      {err}
    ) } -
  • -
    - ); - } - - return ( - <> - { props.errors.length > 0 && !props.below ? renderError() : "" } - { props.children } - { props.errors.length > 0 && props.below ? renderError() : "" } - - ); -} diff --git a/web/fling/src/components/admin/FlingAdmin.jsx b/web/fling/src/components/admin/FlingAdmin.jsx index 50ce0ca..41d6784 100644 --- a/web/fling/src/components/admin/FlingAdmin.jsx +++ b/web/fling/src/components/admin/FlingAdmin.jsx @@ -1,24 +1,41 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { useDispatch } from "react-redux"; +import { useParams } from 'react-router-dom'; + +import { retrieveFlings, setActiveFling } from "../../redux/actions"; import Navbar from './Navbar'; import FlingList from './FlingList'; import FlingContent from './FlingContent'; -import {useParams} from 'react-router-dom'; - export default function FlingAdmin() { - let { fling } = useParams(); + const { flingId } = useParams(); + const dispatch = useDispatch(); - return( -
    - + useEffect(() => { + dispatch(retrieveFlings()); + }, [dispatch]); -
    -
    -
    -
    -
    + useEffect(() => { + if (flingId) { + dispatch(setActiveFling(flingId)) + } + }, [flingId, dispatch]); + + return ( +
    + + +
    +
    +
    + +
    +
    +
    - ); +
    +
    + ); } diff --git a/web/fling/src/components/admin/FlingList.jsx b/web/fling/src/components/admin/FlingList.jsx index 99da35d..6a6f1a8 100644 --- a/web/fling/src/components/admin/FlingList.jsx +++ b/web/fling/src/components/admin/FlingList.jsx @@ -1,33 +1,18 @@ -import log from 'loglevel'; -import React, {useState, useEffect} from 'react'; - -import {FlingClient} from '../../util/fc'; +import React from 'react'; +import { useSelector } from "react-redux"; import FlingTile from './FlingTile'; -export default function FlingList(props) { - const [flings, setFlings] = useState([]); - useEffect(() => { - let flingClient = new FlingClient(); - flingClient.getFlings() - .then(flings => { - let newFlings = []; - for (let fling of flings) { - let flingTile = ; - newFlings.push(flingTile); - } - setFlings(newFlings); - }).catch(log.error); - }, []); +export default function FlingList() { + const flings = useSelector((store) => store.flings.flings); - return( + return (
    - {log.info(`Got active fling: ${props.activeFling}`)}
    My Flings
    - {flings} + {flings.map(fling => )}
    ); diff --git a/web/fling/src/components/admin/FlingTile.jsx b/web/fling/src/components/admin/FlingTile.jsx index fd5f00f..c46f82a 100644 --- a/web/fling/src/components/admin/FlingTile.jsx +++ b/web/fling/src/components/admin/FlingTile.jsx @@ -1,84 +1,92 @@ import log from 'loglevel'; -import React, {useRef} from 'react'; +import React, { useRef } from 'react'; import classNames from 'classnames'; -import {NavLink} from "react-router-dom"; +import { NavLink } from "react-router-dom"; -import {flingClient} from '../../util/flingclient'; +import { flingClient } from '../../util/flingclient'; function TileAction(props) { - let shareUrlRef = useRef(null); + let shareUrlRef = useRef(null); - return( -
    - +
      +
    • +
      + + + +
      +
    • +
    • +
      + +
      +
    • +
    • + -
        -
      • -
        - - -
        -
      • -
      • -
        - -
        -
      • -
      • - -
      • -
      -
    - ); + +
+
+ ); - function copyShareUrl() { - shareUrlRef.current.focus(); - shareUrlRef.current.select(); + function copyShareUrl() { + shareUrlRef.current.focus(); + shareUrlRef.current.select(); - try { - let successful = document.execCommand('copy'); - let msg = successful ? 'successful' : 'unsuccessful'; - console.log('Copying to clipoard ' + msg); - } catch (err) { - log.error("Couldn't copy to clipboard: ", err); - } + try { + let successful = document.execCommand('copy'); + let msg = successful ? 'successful' : 'unsuccessful'; + console.log('Copying to clipoard ' + msg); + } catch (err) { + log.error("Couldn't copy to clipboard: ", err); } + } - async function deleteFling() { - await flingClient.deleteFling(props.fling.id); - await props.refreshFlingListFn(); - } + async function deleteFling() { + await flingClient.deleteFling(props.fling.id); + await props.refreshFlingListFn(); + } - async function toggleShared() { - await flingClient.putFling(props.fling.id, {"sharing": {"shared": !props.fling.shared}}); - await props.refreshFlingListFn(); - } + async function toggleShared() { + await flingClient.putFling(props.fling.id, { "sharing": { "shared": !props.fling.shared } }); + await props.refreshFlingListFn(); + } } export default function FlingTile(props) { - let tileClasses = classNames( - "tile", "tile-centered", "p-2", "c-hand", - {"active": props.activeFling === props.fling.id} - ); + let tileClasses = classNames( + "tile", "tile-centered", "p-2", "c-hand", + { "active": props.activeFling === props.fling.id } + ); - return ( -
-
-
- -
{props.fling.name}
- 14MB · Public · 1 Jan, 2017 -
-
- -
+ return ( +
+
+
+ +
{props.fling.name}
+ + 14MB · Public · 1 Jan, 2017 + +
- ); + +
+
+ ); } diff --git a/web/fling/src/index.js b/web/fling/src/index.js index 1b67669..1172ef8 100644 --- a/web/fling/src/index.js +++ b/web/fling/src/index.js @@ -6,8 +6,9 @@ import ReactDOM from 'react-dom'; import log from 'loglevel'; import { Provider } from 'react-redux'; -import { createStore } from 'redux'; -import reducer from './redux/reducer'; +import { createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; +import rootReducer from './redux/reducers'; import { BrowserRouter } from "react-router-dom"; @@ -19,24 +20,26 @@ import * as serviceWorker from './serviceWorker'; /* Logging Setup */ log.setDefaultLevel(log.levels.TRACE); -if(process.env.REACT_APP_LOGLEVEL) { - log.setLevel(process.env.REACT_APP_LOGLEVEL); +if (process.env.REACT_APP_LOGLEVEL) { + log.setLevel(process.env.REACT_APP_LOGLEVEL); } /* Store setup */ -let store = createStore(reducer, - (window.__REDUX_DEVTOOLS_EXTENSION__ - && window.__REDUX_DEVTOOLS_EXTENSION__())); +let store = createStore(rootReducer, + compose( + applyMiddleware(thunk), + (window.__REDUX_DEVTOOLS_EXTENSION__ + && window.__REDUX_DEVTOOLS_EXTENSION__()))); /* Fling App Setup */ ReactDOM.render( - - - - - - - , + + + + + + + , document.getElementById('root') ); diff --git a/web/fling/src/redux/actionTypes.js b/web/fling/src/redux/actionTypes.js new file mode 100644 index 0000000..ab31344 --- /dev/null +++ b/web/fling/src/redux/actionTypes.js @@ -0,0 +1,3 @@ +export const SET_FLINGS = "SET_FLINGS"; +export const ADD_FLING = "ADD_FLING"; +export const SET_ACTIVE_FLING = "SET_ACTIVE_FLING"; diff --git a/web/fling/src/redux/actions.js b/web/fling/src/redux/actions.js index e69de29..27a09fe 100644 --- a/web/fling/src/redux/actions.js +++ b/web/fling/src/redux/actions.js @@ -0,0 +1,67 @@ +import log from 'loglevel'; + +import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "./actionTypes"; +import { FlingClient } from "../util/fc"; + +function setFlingsAction(flings) { + return { + type: SET_FLINGS, + payload: flings + } +} + +function addFlingAction(fling) { + return { + type: ADD_FLING, + payload: fling + } +} + +function setActiveFlingAction(fling) { + return { + type: SET_ACTIVE_FLING, + payload: fling + } +} + + +function setActiveFling(id) { + return (dispatch, getState) => { + if (!id) { + log.debug(`Not setting active Fling. No id given.`); + return; + } + const { flings: { flings } } = getState(); + let foundFling = flings.find(f => f.id === id); + + if (foundFling) { + log.info(`Found active fling ${id} in local storage`); + dispatch(setActiveFlingAction(foundFling)); + } else { + log.info(`Active fling ${id} not found in local storage. ` + + `Trying to retrieve from remote.`); + + let flingClient = new FlingClient(); + flingClient.getFling(id) + .then(fling => { + dispatch(addFlingAction(fling)); + dispatch(setActiveFlingAction(fling)) + }) + .catch(error => { + log.warn(`Could not find active fling. ` + + `Resetting active fling`); + dispatch(setActiveFlingAction(undefined)); + }) + } + } +} + +function retrieveFlings() { + return (dispatch) => { + let flingClient = new FlingClient(); + flingClient.getFlings() + .then(flings => dispatch(setFlingsAction(flings))); + } +} + +export { retrieveFlings, setActiveFling }; diff --git a/web/fling/src/redux/reducer.js b/web/fling/src/redux/reducer.js deleted file mode 100644 index 1e8d47f..0000000 --- a/web/fling/src/redux/reducer.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function (state = {}, action) { - return; -}; diff --git a/web/fling/src/redux/reducers/flings.js b/web/fling/src/redux/reducers/flings.js new file mode 100644 index 0000000..f5d1f08 --- /dev/null +++ b/web/fling/src/redux/reducers/flings.js @@ -0,0 +1,29 @@ +import produce from "immer"; + +import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "../actionTypes"; + +const initialState = { + // type [fc.Fling] + flings: [], + // fc.Fling.id of the currently active fling + // or null of no fling is active + activeFling: null +} + +export default produce((draft, action) => { + switch (action.type) { + case SET_FLINGS: + draft.flings = action.payload; + break; + case ADD_FLING: + draft.flings.push(action.payload); + break; + case SET_ACTIVE_FLING: + draft.activeFling = action.payload; + break; + default: + break; + } + return draft; + +}, initialState); diff --git a/web/fling/src/redux/reducers/index.js b/web/fling/src/redux/reducers/index.js new file mode 100644 index 0000000..d4756e5 --- /dev/null +++ b/web/fling/src/redux/reducers/index.js @@ -0,0 +1,4 @@ +import { combineReducers } from "redux"; +import flings from "./flings"; + +export default combineReducers({ flings }); diff --git a/web/fling/src/redux/selectorTypes.js b/web/fling/src/redux/selectorTypes.js new file mode 100644 index 0000000..0d0fda6 --- /dev/null +++ b/web/fling/src/redux/selectorTypes.js @@ -0,0 +1,3 @@ +export const FLING_FILTERS = { + ALL: "all" +} diff --git a/web/fling/src/redux/selectors.js b/web/fling/src/redux/selectors.js new file mode 100644 index 0000000..e69de29 diff --git a/web/fling/src/redux/selectors/flingSelectors.js b/web/fling/src/redux/selectors/flingSelectors.js new file mode 100644 index 0000000..a6126f2 --- /dev/null +++ b/web/fling/src/redux/selectors/flingSelectors.js @@ -0,0 +1,10 @@ +import { FLING_FILTERS } from "../selectorTypes"; + +export const flingSelector = (store, flingFilter) => { + switch(flingFilter) { + case FLING_FILTERS.ALL: + return store.flings.flings; + default: + return []; + } +} diff --git a/web/fling/src/redux/state.js b/web/fling/src/redux/state.js deleted file mode 100644 index 4e6c41c..0000000 --- a/web/fling/src/redux/state.js +++ /dev/null @@ -1,4 +0,0 @@ -export default { - flings: [], - currentFling: undefined, -}; diff --git a/web/fling/src/util/fc.js b/web/fling/src/util/fc.js index 11445d7..905186a 100644 --- a/web/fling/src/util/fc.js +++ b/web/fling/src/util/fc.js @@ -1,5 +1,12 @@ +/* + * Shim for the fling API which sets a bearer token for every request + */ import * as fc from '@fling/flingclient'; +/* + * Construct a client configuration with either the given token, or, if token is + * undefined or null, token retrieved from the session storage. + */ let clientConfig = (token) => { let config = new fc.ApiClient(); config.basePath = process.env.REACT_APP_API.replace(/\/+$/, ''); From 16b3318f92a47280d8a5367bf4666579063d85a2 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 19 Jul 2020 19:30:33 +0200 Subject: [PATCH 19/30] Delete fling, fix race conditions --- examples/querysheet.http | 24 +++++++- .../archive/impl/FileSystemArchive.java | 6 +- .../archive/FileSystemArchiveTest.java | 9 +++ web/fling/src/components/admin/FlingAdmin.jsx | 2 +- web/fling/src/components/admin/FlingTile.jsx | 55 +++++++++---------- web/fling/src/redux/actionTypes.js | 1 + web/fling/src/redux/actions.js | 38 ++++++++++--- web/fling/src/redux/reducers/flings.js | 15 ++++- web/fling/src/redux/selectorTypes.js | 3 - web/fling/src/redux/selectors.js | 0 .../src/redux/selectors/flingSelectors.js | 10 ---- 11 files changed, 109 insertions(+), 54 deletions(-) delete mode 100644 web/fling/src/redux/selectorTypes.js delete mode 100644 web/fling/src/redux/selectors.js delete mode 100644 web/fling/src/redux/selectors/flingSelectors.js diff --git a/examples/querysheet.http b/examples/querysheet.http index a7b16a6..4938196 100644 --- a/examples/querysheet.http +++ b/examples/querysheet.http @@ -4,6 +4,7 @@ # Authenticate as user POST http://localhost:8080/api/auth/user +Content-Type: application/json {"shareId": "shareId", "authCode":"secret"} -> jq-set-var :token . @@ -15,9 +16,30 @@ Content-Type: application/json {"adminName": "admin", "adminPassword":"123"} -> run-hook (restclient-set-var ":token" (buffer-substring-no-properties 1 (line-end-position))) +:token = Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTUxNzY4OTksImV4cCI6MTU5NTM1Njg5OSwic3ViIjoiYWRtaW4ifQ.uRh_xBCrBiLQEBah9I8bYWM-Zph-V_pzQVdaGSU5Mlc + # Get all flings GET http://localhost:8080/api/fling -Authorization: Bearer :token +Content-Type: application/json +:token + +# Add a new fling +POST http://localhost:8080/api/fling +Content-Type: application/json +:token +{"name": "Unshared Fling from querysheet", "expirationClicks": 12, "shared": false} + +# Add a new fling +POST http://localhost:8080/api/fling +Content-Type: application/json +:token +{"name": "Fling from querysheet with Auth", "expirationClicks": 12, "shared": true, "authCode": "abc"} + +# Add a new fling +POST http://localhost:8080/api/fling +Content-Type: application/json +:token +{"name": "Fling from querysheet with Auth and very long name", "expirationClicks": 12, "shared": true, "authCode": "abc"} # :flingId = dfc208a3-5924-43b4-aa6a-c263541dca5e diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java index 4189a55..86eb9f8 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java @@ -129,7 +129,11 @@ public class FileSystemArchive implements ArchiveService { Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath); - Files.delete(zipDiskPath); + if(Files.exists(zipDiskPath)) { + Files.delete(zipDiskPath); + } else { + log.warn("No fling disk found at {}", zipDiskPath); + } artifactRepository.findAllByFlingId(flingId).forEach(ar -> ar.setArchived(false)); } diff --git a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java index 0f6b5bb..112b1e1 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/archive/FileSystemArchiveTest.java @@ -4,6 +4,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; import java.io.File; @@ -218,6 +219,14 @@ public class FileSystemArchiveTest { equalTo(false)); } + @Test + public void deleteFling_zipDiskNotFound_noThrow() throws IOException { + assertThat(Files.exists(tempDir.resolve(artifactEntity2.getFling().getId() + ".zip")), + equalTo(false)); + + assertDoesNotThrow(() -> fileSystemArchive.deleteFling(artifactEntity2.getFling().getId())); + } + private void repopulateArchivePath() throws IOException, URISyntaxException { Files.walkFileTree(tempDir, new SimpleFileVisitor() { @Override diff --git a/web/fling/src/components/admin/FlingAdmin.jsx b/web/fling/src/components/admin/FlingAdmin.jsx index 41d6784..2233132 100644 --- a/web/fling/src/components/admin/FlingAdmin.jsx +++ b/web/fling/src/components/admin/FlingAdmin.jsx @@ -18,7 +18,7 @@ export default function FlingAdmin() { useEffect(() => { if (flingId) { - dispatch(setActiveFling(flingId)) + dispatch(setActiveFling(flingId)); } }, [flingId, dispatch]); diff --git a/web/fling/src/components/admin/FlingTile.jsx b/web/fling/src/components/admin/FlingTile.jsx index c46f82a..4a02862 100644 --- a/web/fling/src/components/admin/FlingTile.jsx +++ b/web/fling/src/components/admin/FlingTile.jsx @@ -3,10 +3,26 @@ import React, { useRef } from 'react'; import classNames from 'classnames'; import { NavLink } from "react-router-dom"; -import { flingClient } from '../../util/flingclient'; +import { useSelector, useDispatch } from "react-redux"; + +import { deleteFling } from "../../redux/actions"; function TileAction(props) { let shareUrlRef = useRef(null); + const dispatch = useDispatch(); + + function copyShareUrl() { + shareUrlRef.current.focus(); + shareUrlRef.current.select(); + + try { + let successful = document.execCommand('copy'); + let msg = successful ? 'successful' : 'unsuccessful'; + console.log('Copying to clipoard ' + msg); + } catch (err) { + log.error("Couldn't copy to clipboard: ", err); + } + } return (
@@ -27,8 +43,7 @@ function TileAction(props) {
  • @@ -36,7 +51,7 @@ function TileAction(props) {
  • @@ -44,34 +59,14 @@ function TileAction(props) {
    ); - function copyShareUrl() { - shareUrlRef.current.focus(); - shareUrlRef.current.select(); - - try { - let successful = document.execCommand('copy'); - let msg = successful ? 'successful' : 'unsuccessful'; - console.log('Copying to clipoard ' + msg); - } catch (err) { - log.error("Couldn't copy to clipboard: ", err); - } - } - - async function deleteFling() { - await flingClient.deleteFling(props.fling.id); - await props.refreshFlingListFn(); - } - - async function toggleShared() { - await flingClient.putFling(props.fling.id, { "sharing": { "shared": !props.fling.shared } }); - await props.refreshFlingListFn(); - } } export default function FlingTile(props) { + const activeFling = useSelector((state) => state.flings.activeFling); + let tileClasses = classNames( "tile", "tile-centered", "p-2", "c-hand", - { "active": props.activeFling === props.fling.id } + { "active": activeFling ? activeFling.id === props.fling.id : false } ); return ( @@ -81,11 +76,13 @@ export default function FlingTile(props) {
    {props.fling.name}
    - 14MB · Public · 1 Jan, 2017 + {`${props.fling.shared ? "Shared" : "Private"}` + + ` · ${(new Date(props.fling.creationTime)).toLocaleDateString()}` + + ` · ${props.fling.authCode ? "Protected" : "Unprotected"}`}
    - + ); diff --git a/web/fling/src/redux/actionTypes.js b/web/fling/src/redux/actionTypes.js index ab31344..8519a5f 100644 --- a/web/fling/src/redux/actionTypes.js +++ b/web/fling/src/redux/actionTypes.js @@ -1,3 +1,4 @@ export const SET_FLINGS = "SET_FLINGS"; export const ADD_FLING = "ADD_FLING"; +export const DELETE_FLING = "DELETE_FLING"; export const SET_ACTIVE_FLING = "SET_ACTIVE_FLING"; diff --git a/web/fling/src/redux/actions.js b/web/fling/src/redux/actions.js index 27a09fe..0e59239 100644 --- a/web/fling/src/redux/actions.js +++ b/web/fling/src/redux/actions.js @@ -24,15 +24,14 @@ function setActiveFlingAction(fling) { } } - function setActiveFling(id) { return (dispatch, getState) => { if (!id) { log.debug(`Not setting active Fling. No id given.`); - return; + return false; } const { flings: { flings } } = getState(); - let foundFling = flings.find(f => f.id === id); + const foundFling = flings.find(f => f.id === id); if (foundFling) { log.info(`Found active fling ${id} in local storage`); @@ -48,7 +47,7 @@ function setActiveFling(id) { dispatch(setActiveFlingAction(fling)) }) .catch(error => { - log.warn(`Could not find active fling. ` + + log.warn(`Could not find active fling: ${error} \n` + `Resetting active fling`); dispatch(setActiveFlingAction(undefined)); }) @@ -57,11 +56,34 @@ function setActiveFling(id) { } function retrieveFlings() { - return (dispatch) => { - let flingClient = new FlingClient(); + return (dispatch, getState) => { + const { flings: { activeFling } } = getState(); + const flingClient = new FlingClient(); + flingClient.getFlings() - .then(flings => dispatch(setFlingsAction(flings))); + .then(flings => { + dispatch(setFlingsAction(flings)); + if (activeFling) { + dispatch(setActiveFling(activeFling.id)); + } + }); } } -export { retrieveFlings, setActiveFling }; +function deleteFling(id) { + return (dispatch, getState) => { + if (!id) { + log.debug(`Not deleting Fling. No id given.`); + return; + } + + const flingClient = new FlingClient(); + + flingClient.deleteFling(id) + .then(() => dispatch(retrieveFlings())) + .catch(error => + log.error(`Could not delete fling ${id}: ${error}`)); + } +} + +export { retrieveFlings, setActiveFling, deleteFling }; diff --git a/web/fling/src/redux/reducers/flings.js b/web/fling/src/redux/reducers/flings.js index f5d1f08..31f3ac8 100644 --- a/web/fling/src/redux/reducers/flings.js +++ b/web/fling/src/redux/reducers/flings.js @@ -1,3 +1,4 @@ +import log from "loglevel"; import produce from "immer"; import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "../actionTypes"; @@ -16,7 +17,19 @@ export default produce((draft, action) => { draft.flings = action.payload; break; case ADD_FLING: - draft.flings.push(action.payload); + // Check storage again here, otherwise there could be a race + // condition due to async calls of SET_FLINGS and ADD_FLING + let foundFlingIdx = draft.flings.findIndex(fling => + fling.id === action.payload.id); + + if (foundFlingIdx === -1) { + log.debug(`Adding new fling with id ${action.payload.id}`) + draft.flings.push(action.payload); + } else { + log.debug(`Fling already exists. ` + + `Updating fling with id ${action.payload.id}`) + draft.flings[foundFlingIdx] = action.payload + } break; case SET_ACTIVE_FLING: draft.activeFling = action.payload; diff --git a/web/fling/src/redux/selectorTypes.js b/web/fling/src/redux/selectorTypes.js deleted file mode 100644 index 0d0fda6..0000000 --- a/web/fling/src/redux/selectorTypes.js +++ /dev/null @@ -1,3 +0,0 @@ -export const FLING_FILTERS = { - ALL: "all" -} diff --git a/web/fling/src/redux/selectors.js b/web/fling/src/redux/selectors.js deleted file mode 100644 index e69de29..0000000 diff --git a/web/fling/src/redux/selectors/flingSelectors.js b/web/fling/src/redux/selectors/flingSelectors.js deleted file mode 100644 index a6126f2..0000000 --- a/web/fling/src/redux/selectors/flingSelectors.js +++ /dev/null @@ -1,10 +0,0 @@ -import { FLING_FILTERS } from "../selectorTypes"; - -export const flingSelector = (store, flingFilter) => { - switch(flingFilter) { - case FLING_FILTERS.ALL: - return store.flings.flings; - default: - return []; - } -} From 3ae0e912b75aed2ee03d70705eff9fa9b89110f1 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 19 Jul 2020 21:22:00 +0200 Subject: [PATCH 20/30] Remove faulty python package publishing code --- .drone.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.drone.yml b/.drone.yml index 87e3192..5f4ff04 100644 --- a/.drone.yml +++ b/.drone.yml @@ -45,17 +45,6 @@ steps: -o flingclient.py --enable-post-process-file - cd flingclient.py - - |+ - cat << EOF - [distutils] - index-servers = - nexus - [private-repository] - repository = - username = - password = - EOF >> .pypirc - - cat .pypirc - cd .. # JavaScript client - openapi-generator generate From 3860ed91776a7020fa6ffd0abb88ec262a746a70 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 19 Jul 2020 23:45:22 +0200 Subject: [PATCH 21/30] Upload with new API --- examples/querysheet.http | 6 + .../persistence/entities/ArtifactEntity.java | 3 +- .../repositories/FlingRepository.java | 2 +- .../security/FlingWebSecurityConfigurer.java | 6 +- .../friedl/fling/service/ArtifactService.java | 3 +- .../archive/impl/FileSystemArchive.java | 19 +- web/fling/package-lock.json | 6488 ++++++----------- .../src/components/admin/FlingContent.jsx | 77 +- web/fling/src/components/admin/FlingTile.jsx | 2 +- web/fling/src/components/admin/Login.jsx | 36 +- web/fling/src/components/admin/Upload.jsx | 395 +- web/fling/src/redux/reducers/artifacts.js | 39 + web/fling/src/util/fn.js | 19 + 13 files changed, 2643 insertions(+), 4452 deletions(-) create mode 100644 web/fling/src/redux/reducers/artifacts.js create mode 100644 web/fling/src/util/fn.js diff --git a/examples/querysheet.http b/examples/querysheet.http index 4938196..a9e3a3c 100644 --- a/examples/querysheet.http +++ b/examples/querysheet.http @@ -23,6 +23,12 @@ GET http://localhost:8080/api/fling Content-Type: application/json :token +# Add a new fling +POST http://localhost:8080/api/fling +Content-Type: application/json +:token +{"name": "Shared Fling from querysheet", "expirationClicks": 12, "shared": true} + # Add a new fling POST http://localhost:8080/api/fling Content-Type: application/json diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java index 3e9462d..c1934f1 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/ArtifactEntity.java @@ -9,6 +9,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.UniqueConstraint; import javax.persistence.Version; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -16,7 +17,7 @@ import lombok.Getter; import lombok.Setter; @Entity -@Table(name = "Artifact") +@Table(name = "Artifact", uniqueConstraints = @UniqueConstraint(columnNames = {"fling_id", "path"})) @Getter @Setter public class ArtifactEntity { diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java index 99ce0e6..8fd3118 100644 --- a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java +++ b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/FlingRepository.java @@ -11,6 +11,6 @@ public interface FlingRepository extends JpaRepository { FlingEntity findByShareId(String shareId); - @Query("SELECT fe FROM FlingEntity fe JOIN ArtifactEntity ae ON fe.id=ae.id WHERE ae.id=:artifactId") + @Query("SELECT fe FROM FlingEntity fe JOIN ArtifactEntity ae ON fe.id=ae.fling.id WHERE ae.id=:artifactId") FlingEntity findByArtifactId(UUID artifactId); } diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index 7ff70ad..e5a0d0f 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -109,15 +109,15 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { /***************************************/ .authorizeRequests() .antMatchers(HttpMethod.GET, "/api/artifacts/{artifactId}/**") - .access("@authorizationService.allowArtifactAccess(#artifactId, token)") + .access("@authorizationService.allowArtifactAccess(#artifactId, authentication)") .and() .authorizeRequests() .antMatchers(HttpMethod.POST, "/api/artifacts/{artifactId}/data") - .access("@authorizationService.allowArtifactUpload(#artifactId, token)") + .access("@authorizationService.allowArtifactUpload(#artifactId, authentication)") .and() .authorizeRequests() .antMatchers(HttpMethod.DELETE, "/api/artifacts/{artifactId}") - .access("@authorizationService.allowArtifactUpload(#artifactId, token)"); + .access("@authorizationService.allowArtifactUpload(#artifactId, authentication)"); //@formatter:on } diff --git a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java index 1a848de..b839c2b 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java @@ -56,7 +56,8 @@ public class ArtifactService { */ public ArtifactDto create(UUID flingId, ArtifactDto artifactDto) { FlingEntity flingEntity = flingRepository.getOne(flingId); - + + log.debug("Creating new ArtifactEntity for ArtifactDto[.path={}]", artifactDto.getPath()); ArtifactEntity artifactEntity = artifactMapper.map(artifactDto); artifactEntity.setFling(flingEntity); artifactEntity = artifactRepository.save(artifactEntity); diff --git a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java index 86eb9f8..16c0123 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java +++ b/service/fling/src/main/java/net/friedl/fling/service/archive/impl/FileSystemArchive.java @@ -89,14 +89,16 @@ public class FileSystemArchive implements ArchiveService { public void storeArtifact(UUID artifactId, InputStream artifactStream) throws IOException { log.debug("Storing artifact {}", artifactId); - setArchived(artifactId, false); - FileSystem zipDisk = getZipDisk(artifactId); - Files.copy(artifactStream, getZipDiskPath(artifactId, zipDisk), - StandardCopyOption.REPLACE_EXISTING); + synchronized (filesystems) { + setArchived(artifactId, false); + FileSystem zipDisk = getZipDisk(artifactId); + Files.copy(artifactStream, getZipDiskPath(artifactId, zipDisk), + StandardCopyOption.REPLACE_EXISTING); - // we need to close the zipDisk in order to flush it to disk - closeZipDisk(artifactId); - setArchived(artifactId, true); + // we need to close the zipDisk in order to flush it to disk + closeZipDisk(artifactId); + setArchived(artifactId, true); + } } @Override @@ -129,7 +131,7 @@ public class FileSystemArchive implements ArchiveService { Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip"); log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath); - if(Files.exists(zipDiskPath)) { + if (Files.exists(zipDiskPath)) { Files.delete(zipDiskPath); } else { log.warn("No fling disk found at {}", zipDiskPath); @@ -142,7 +144,6 @@ public class FileSystemArchive implements ArchiveService { private void setArchived(UUID artifactId, boolean archived) { ArtifactEntity artifactEntity = artifactRepository.getOne(artifactId); artifactEntity.setArchived(archived); - log.debug("Artifact[.id={}] set to {} archived", artifactId, archived ? "" : "not"); } diff --git a/web/fling/package-lock.json b/web/fling/package-lock.json index 0404fa6..b577473 100644 --- a/web/fling/package-lock.json +++ b/web/fling/package-lock.json @@ -5,157 +5,37 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.10.4.tgz", - "integrity": "sha512-xX99K4V1BzGJdQANK5cwK+EpF1vP9gvqhn+iWvG+TubCjecplW7RSQimJ2jcCvu6fnK5pY6mZMdu6EWTj32QVA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.10.5.tgz", + "integrity": "sha512-j9H9qSf3kLdM0Ao3aGPbGZ73mEA9XazuupcS6cDGWuiyAcANoguhP0r2Lx32H5JGw4sSSoHG3x/mxVnHgvOoyA==", "requires": { "chokidar": "^2.1.8", "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" - }, - "dependencies": { - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.10.4" } }, "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", + "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", "requires": { - "browserslist": "^4.9.1", + "browserslist": "^4.12.0", "invariant": "^2.2.4", "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/core": { @@ -181,296 +61,296 @@ "source-map": "^0.5.0" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, "@babel/generator": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", - "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", "requires": { - "@babel/types": "^7.9.5", + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", - "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/types": "^7.9.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz", - "integrity": "sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.5.tgz", + "integrity": "sha512-Buewnx6M4ttG+NLkKyt7baQn7ScC/Td+e99G914fRU8fGIUivDDgVIQeDHFa5e4CRSJQt58WpNHhsAZgtzVhsg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/types": "^7.10.5" } }, "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.5.tgz", - "integrity": "sha512-IipaxGaQmW4TfWoXdqjY0TzoXQ1HRS0kPpEgvjosb3u7Uedcq297xFqDQiCcQtRRwzIMif+N1MLVI8C5a4/PAA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", "requires": { - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.5" } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" }, "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", - "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-decorators": { @@ -484,76 +364,85 @@ } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz", - "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.9.5" + "@babel/plugin-transform-parameters": "^7.10.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", + "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -564,12 +453,20 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/plugin-syntax-decorators": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz", - "integrity": "sha512-8Hg4dNNT9/LcA1zQlfwuKR8BUc/if7Q7NkTam9sGTcJphLwpf2g4S42uhspQrIrR+dpzE0dtTqBVFoHl8GtnnQ==", + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz", + "integrity": "sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-dynamic-import": { @@ -581,11 +478,11 @@ } }, "@babel/plugin-syntax-flow": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz", - "integrity": "sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.4.tgz", + "integrity": "sha512-yxQsX1dJixF4qEEdzVbst3SZQ58Nrooz8NV9Z9GL4byTE25BvJgl5lf0RECUf0fh28rZBb/RYTWn/eeKwCMrZQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-json-strings": { @@ -597,11 +494,11 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", + "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-nullish-coalescing-operator": { @@ -613,11 +510,11 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -645,111 +542,110 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-typescript": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.3.tgz", - "integrity": "sha512-GO1MQ/SGGGoiEXY0e0bSpHimJvxqB7lktLLIq2pv8xG7WZ8IMEle74jIe1FhprHBWjwjZtXHkycDLZXIWM5Wfg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz", + "integrity": "sha512-oSAEz1YkBCAKr5Yiq8/BNtvSAPwkp/IyUnwZogd8p+F0RuYQQrLeRUzIQhueQTTBy/F+a40uS7OFKxnkRvmvFQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", + "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz", - "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz", - "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-flow-strip-types": { @@ -762,190 +658,199 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4" } }, "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" } }, "@babel/plugin-transform-parameters": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz", - "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-constant-elements": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.9.0.tgz", - "integrity": "sha512-wXMXsToAUOxJuBBEHajqKLFWcCkOSLshTI2ChCFFj1zDd7od4IOxiwLCOObNUvOpkxLpjIuaIdBMmNt6ocCPAw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz", + "integrity": "sha512-cYmQBW1pXrqBte1raMkAulXmi7rjg3VI6ZLg9QIic8Hq7BtYXaWuZSxsr2siOMI6SWwpxjWfnwhTUrd7JlAV7g==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", - "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz", + "integrity": "sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", - "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz", + "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==", "requires": { - "@babel/helper-builder-react-jsx": "^7.9.0", - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz", - "integrity": "sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz", + "integrity": "sha512-RM3ZAd1sU1iQ7rI2dhrZRZGv0aqzNQMbkIUCS1txYpi9wHQ2ZHNjo5TwX+UD6pvFW4AbWqLVYvKy5qJSAyRGjQ==", "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz", - "integrity": "sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz", + "integrity": "sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz", - "integrity": "sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz", + "integrity": "sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz", + "integrity": "sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-runtime": { @@ -957,148 +862,146 @@ "@babel/helper-plugin-utils": "^7.8.3", "resolve": "^1.8.1", "semver": "^5.5.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", + "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typescript": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz", - "integrity": "sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.5.tgz", + "integrity": "sha512-YCyYsFrrRMZ3qR7wRwtSSJovPG5vGyG4ZdcSAivGwTfoasMp3VOB/AKhohu3dFtmB4cCDcsndCSxGtrdliCsZQ==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-typescript": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-typescript": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.5.tgz", - "integrity": "sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", + "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.5", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/compat-data": "^7.10.4", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-json-strings": "^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.5", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.9.5", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.9.5", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.10.4", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.5", - "browserslist": "^4.9.1", + "@babel/types": "^7.10.4", + "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/preset-modules": { @@ -1114,16 +1017,17 @@ } }, "@babel/preset-react": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.4.tgz", - "integrity": "sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz", + "integrity": "sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-transform-react-display-name": "^7.8.3", - "@babel/plugin-transform-react-jsx": "^7.9.4", - "@babel/plugin-transform-react-jsx-development": "^7.9.0", - "@babel/plugin-transform-react-jsx-self": "^7.9.0", - "@babel/plugin-transform-react-jsx-source": "^7.9.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.10.4", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@babel/plugin-transform-react-jsx-development": "^7.10.4", + "@babel/plugin-transform-react-jsx-self": "^7.10.4", + "@babel/plugin-transform-react-jsx-source": "^7.10.4", + "@babel/plugin-transform-react-pure-annotations": "^7.10.4" } }, "@babel/preset-typescript": { @@ -1136,55 +1040,70 @@ } }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", + "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz", - "integrity": "sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.5.tgz", + "integrity": "sha512-RMafpmrNB5E/bwdSphLr8a8++9TosnyJp98RZzI6VOx2R2CCMpsXXXRvmI700O9oEKpXdZat6oEK68/F0zjd4A==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/traverse": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", - "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.5", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.5", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "@babel/types": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", - "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1210,7 +1129,7 @@ "@fling/flingclient": { "version": "0.1.0-snapshot", "resolved": "https://nexus.friedl.net/repository/npm-private/@fling/flingclient/-/flingclient-0.1.0-snapshot.tgz", - "integrity": "sha512-Ws4M0st41sb8gQz07k3ygEXoqx5GtSmH9rr/RzrJhQELt0dVxNi8qJY3VcJxV6svpYvwKDacRyy+aXnNnOG6/w==", + "integrity": "sha512-L7csowwIzJx6A3Jgm/ejrgoClPyTimrMGM2ezChklgV/FI/4OJAuk3eYJ8IM9rkT59/Zm4B8z6xmM4GOquuncQ==", "requires": { "@babel/cli": "^7.0.0", "superagent": "3.7.0" @@ -1299,6 +1218,14 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -1523,6 +1450,13 @@ "@svgr/plugin-jsx": "^4.3.3", "camelcase": "^5.3.1", "cosmiconfig": "^5.2.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } } }, "@svgr/hast-util-to-babel-ast": { @@ -1584,9 +1518,9 @@ }, "dependencies": { "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", @@ -1595,13 +1529,18 @@ } }, "@types/yargs": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", - "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", "requires": { "@types/yargs-parser": "*" } }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1611,15 +1550,6 @@ "color-convert": "^2.0.1" } }, - "aria-query": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.0.2.tgz", - "integrity": "sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w==", - "requires": { - "@babel/runtime": "^7.7.4", - "@babel/runtime-corejs3": "^7.7.4" - } - }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -1648,11 +1578,11 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -1700,9 +1630,9 @@ "integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==" }, "@types/babel__core": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", - "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", @@ -1729,9 +1659,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.10.tgz", - "integrity": "sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", "requires": { "@babel/types": "^7.3.0" } @@ -1746,25 +1676,19 @@ "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==" }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" - }, "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "requires": { - "@types/events": "*", "@types/minimatch": "*", "@types/node": "*" } }, "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" }, "@types/istanbul-lib-report": { "version": "3.0.0", @@ -1775,18 +1699,18 @@ } }, "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", "requires": { "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" } }, "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==" + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==" }, "@types/minimatch": { "version": "3.0.3", @@ -1794,9 +1718,9 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.0.tgz", - "integrity": "sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A==" + "version": "14.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", + "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==" }, "@types/parse-json": { "version": "4.0.0", @@ -1809,23 +1733,23 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, "@types/react": { - "version": "16.9.34", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz", - "integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==", + "version": "16.9.43", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz", + "integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, "@types/react-dom": { - "version": "16.9.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.6.tgz", - "integrity": "sha512-S6ihtlPMDotrlCJE9ST1fRmYrQNNwfgL61UB4I1W7M6kPulUKx9fXAleW5zpdIjUQ4fTaaog8uERezjsGUj9HQ==", + "version": "16.9.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", + "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", "requires": { "@types/react": "*" } @@ -1854,9 +1778,9 @@ }, "dependencies": { "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", @@ -1865,13 +1789,18 @@ } }, "@types/yargs": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", - "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", "requires": { "@types/yargs-parser": "*" } }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1909,11 +1838,11 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -1930,9 +1859,9 @@ } }, "@types/yargs": { - "version": "13.0.8", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", - "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", + "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", "requires": { "@types/yargs-parser": "*" } @@ -1943,50 +1872,70 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, "@typescript-eslint/eslint-plugin": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz", - "integrity": "sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", + "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==", "requires": { - "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/experimental-utils": "2.34.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" } }, "@typescript-eslint/experimental-utils": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz", - "integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.28.0", + "@typescript-eslint/typescript-estree": "2.34.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.28.0.tgz", - "integrity": "sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", + "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", "requires": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.28.0", - "@typescript-eslint/typescript-estree": "2.28.0", + "@typescript-eslint/experimental-utils": "2.34.0", + "@typescript-eslint/typescript-estree": "2.34.0", "eslint-visitor-keys": "^1.1.0" } }, "@typescript-eslint/typescript-estree": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz", - "integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", "requires": { "debug": "^4.1.1", "eslint-visitor-keys": "^1.1.0", "glob": "^7.1.6", "is-glob": "^4.0.1", "lodash": "^4.17.15", - "semver": "^6.3.0", + "semver": "^7.3.2", "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } } }, "@webassemblyjs/ast": { @@ -2177,9 +2126,9 @@ } }, "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==" }, "acorn-globals": { "version": "4.3.4", @@ -2264,9 +2213,9 @@ } }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2280,9 +2229,9 @@ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz", + "integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==" }, "alphanum-sort": { "version": "1.0.2", @@ -2320,9 +2269,9 @@ "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "ansi-styles": { "version": "3.2.1", @@ -2339,6 +2288,16 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "aproba": { @@ -2353,30 +2312,6 @@ "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "argparse": { @@ -2388,12 +2323,12 @@ } }, "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" } }, "arity-n": { @@ -2494,6 +2429,13 @@ "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "assert": { @@ -2558,17 +2500,17 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autoprefixer": { - "version": "9.7.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz", - "integrity": "sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==", + "version": "9.8.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.5.tgz", + "integrity": "sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg==", "requires": { - "browserslist": "^4.11.1", - "caniuse-lite": "^1.0.30001039", - "chalk": "^2.4.2", + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001097", + "colorette": "^1.2.0", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.27", - "postcss-value-parser": "^4.0.3" + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" } }, "aws-sign2": { @@ -2577,9 +2519,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" }, "axios": { "version": "0.19.2", @@ -2587,35 +2529,12 @@ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", "requires": { "follow-redirects": "1.5.10" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "axobject-query": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", - "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" }, "babel-code-frame": { "version": "6.26.0", @@ -2627,11 +2546,6 @@ "js-tokens": "^3.0.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -2654,14 +2568,6 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -2714,19 +2620,12 @@ "mkdirp": "^0.5.3", "pify": "^4.0.1", "schema-utils": "^2.6.5" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } } }, "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "requires": { "object.assign": "^4.1.0" } @@ -2749,36 +2648,6 @@ "requires": { "locate-path": "^3.0.0" } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" } } }, @@ -2899,6 +2768,50 @@ "babel-plugin-transform-react-remove-prop-types": "0.4.24" }, "dependencies": { + "@babel/plugin-proposal-class-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", + "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", + "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", + "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", + "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/preset-env": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", @@ -2986,11 +2899,6 @@ "requires": { "regenerator-runtime": "^0.13.4" } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -3072,11 +2980,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -3104,9 +3007,18 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } }, "block-stream": { "version": "0.0.9", @@ -3122,9 +3034,9 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==" }, "body-parser": { "version": "1.19.0", @@ -3148,19 +3060,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -3288,20 +3187,46 @@ "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "browserify-zlib": { @@ -3313,14 +3238,14 @@ } }, "browserslist": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz", - "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", "requires": { - "caniuse-lite": "^1.0.30001038", - "electron-to-chromium": "^1.3.390", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" } }, "bser": { @@ -3391,13 +3316,18 @@ "unique-filename": "^1.1.1" }, "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { - "glob": "^7.1.3" + "yallist": "^3.0.2" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -3453,9 +3383,9 @@ } }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" }, "camelcase-keys": { "version": "2.1.0", @@ -3464,13 +3394,6 @@ "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - } } }, "caniuse-api": { @@ -3485,9 +3408,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001042", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz", - "integrity": "sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw==" + "version": "1.0.30001103", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001103.tgz", + "integrity": "sha512-EJkTPrZrgy712tjZ7GQDye5A67SQOyNS6X9b6GS/e5QFu5Renv5qfkx3GHq1S+vObxKzbWWYuPO/7nt4kYW/gA==" }, "capture-exit": { "version": "2.0.0", @@ -3523,63 +3446,22 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - } + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" } }, "chownr": { @@ -3664,9 +3546,9 @@ } }, "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, "cliui": { "version": "5.0.0", @@ -3678,11 +3560,6 @@ "wrap-ansi": "^5.1.0" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -3697,6 +3574,14 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -3710,6 +3595,16 @@ "kind-of": "^3.0.2", "lazy-cache": "^1.0.3", "shallow-clone": "^0.1.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "co": { @@ -3772,6 +3667,11 @@ "simple-swizzle": "^0.2.2" } }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3781,9 +3681,9 @@ } }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" }, "common-tags": { "version": "1.8.0", @@ -3828,21 +3728,6 @@ "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "concat-map": { @@ -3859,30 +3744,6 @@ "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "confusing-browser-globals": { @@ -4009,6 +3870,17 @@ "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } } }, "create-ecdh": { @@ -4018,6 +3890,13 @@ "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "create-hash": { @@ -4046,22 +3925,12 @@ } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", + "lru-cache": "^4.0.1", "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "crypto-browserify": { @@ -4167,10 +4036,10 @@ "schema-utils": "^2.6.0" }, "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" } } }, @@ -4215,9 +4084,9 @@ } }, "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", + "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==" }, "css.escape": { "version": "1.5.1", @@ -4348,9 +4217,9 @@ } }, "csstype": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", - "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz", + "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==" }, "currently-unhandled": { "version": "0.4.1", @@ -4410,11 +4279,11 @@ } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, "decamelize": { @@ -4496,11 +4365,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -4541,11 +4405,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" } } }, @@ -4595,21 +4454,6 @@ "requires": { "address": "^1.0.1", "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "diff-sequences": { @@ -4625,6 +4469,13 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "dir-glob": { @@ -4634,6 +4485,21 @@ "requires": { "arrify": "^1.0.1", "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } } }, "dns-equal": { @@ -4771,30 +4637,6 @@ "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "ecc-jsbn": { @@ -4812,14 +4654,14 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.413", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.413.tgz", - "integrity": "sha512-Jm1Rrd3siqYHO3jftZwDljL2LYQafj3Kki5r+udqE58d0i91SkjItVJ5RwlJn9yko8i7MOcoidVKjQlgSdd1hg==" + "version": "1.3.501", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.501.tgz", + "integrity": "sha512-tyzuKaV2POw2mtqBBzQGNBojMZzH0MRu8bT8T/50x+hWeucyG/9pkgAATy+PcM2ySNM9+8eG2VllY9c6j4i+bg==" }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -4828,12 +4670,19 @@ "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "emojis-list": { "version": "3.0.0", @@ -4854,9 +4703,9 @@ } }, "enhanced-resolve": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", - "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "requires": { "graceful-fs": "^4.1.2", "memory-fs": "^0.5.0", @@ -4871,35 +4720,13 @@ "errno": "^0.1.3", "readable-stream": "^2.0.1" } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } } } }, "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" }, "errno": { "version": "0.1.7", @@ -4918,21 +4745,21 @@ } }, "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -4974,6 +4801,11 @@ "ext": "^1.1.2" } }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4985,9 +4817,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "requires": { "esprima": "^4.0.1", "estraverse": "^4.2.0", @@ -5048,6 +4880,33 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, "eslint-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", @@ -5056,6 +4915,14 @@ "eslint-visitor-keys": "^1.1.0" } }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -5073,6 +4940,11 @@ "resolve-from": "^4.0.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", @@ -5082,6 +4954,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -5094,27 +4979,12 @@ } }, "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "requires": { "debug": "^2.6.9", "resolve": "^1.13.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "eslint-loader": { @@ -5138,18 +5008,48 @@ "pkg-dir": "^2.0.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "requires": { - "ms": "2.0.0" + "locate-path": "^2.0.0" } }, - "ms": { + "locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "pkg-dir": { "version": "2.0.0", @@ -5188,14 +5088,6 @@ "resolve": "^1.12.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -5205,6 +5097,14 @@ "isarray": "^1.0.0" } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -5216,19 +5116,41 @@ "strip-bom": "^3.0.0" } }, - "ms": { + "locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "requires": { - "error-ex": "^1.2.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -5260,6 +5182,11 @@ "find-up": "^2.0.0", "read-pkg": "^2.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" } } }, @@ -5279,10 +5206,19 @@ "jsx-ast-utils": "^2.2.1" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" } } }, @@ -5313,13 +5249,10 @@ "esutils": "^2.0.2" } }, - "resolve": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.1.tgz", - "integrity": "sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig==", - "requires": { - "path-parse": "^1.0.6" - } + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -5329,26 +5262,26 @@ "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==" }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "requires": { "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" }, "espree": { "version": "6.2.1", @@ -5404,9 +5337,9 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" }, "events": { "version": "3.1.0", @@ -5447,6 +5380,20 @@ "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } } }, "exit": { @@ -5468,14 +5415,6 @@ "to-regex": "^3.0.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -5491,11 +5430,6 @@ "requires": { "is-extendable": "^0.1.0" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -5554,18 +5488,10 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "qs": { "version": "6.7.0", @@ -5679,11 +5605,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -5693,9 +5614,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "2.2.7", @@ -5708,27 +5629,6 @@ "is-glob": "^4.0.0", "merge2": "^1.2.3", "micromatch": "^3.1.10" - }, - "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - } } }, "fast-json-stable-stringify": { @@ -5787,6 +5687,12 @@ "schema-utils": "^2.5.0" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "filesize": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", @@ -5825,21 +5731,6 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "find-cache-dir": { @@ -5853,11 +5744,12 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { - "locate-path": "^2.0.0" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "flat-cache": { @@ -5868,6 +5760,16 @@ "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + } } }, "flatted": { @@ -5887,46 +5789,22 @@ "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { - "debug": "^3.0.0" + "debug": "=3.1.0" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } } } @@ -5964,17 +5842,100 @@ "worker-rpc": "^0.1.0" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } } } }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6011,30 +5972,6 @@ "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "fs-extra": { @@ -6069,30 +6006,6 @@ "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "fs.realpath": { @@ -6101,10 +6014,14 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "optional": true + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "fstream": { "version": "1.0.12", @@ -6140,39 +6057,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "gaze": { @@ -6238,11 +6122,22 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "requires": { - "is-glob": "^4.0.1" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } } }, "glob-to-regexp": { @@ -6266,13 +6161,6 @@ "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - } } }, "globals": { @@ -6299,6 +6187,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -6317,20 +6210,15 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "gzip-size": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", @@ -6338,13 +6226,6 @@ "requires": { "duplexer": "^0.1.1", "pify": "^4.0.1" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } } }, "handle-thing": { @@ -6439,12 +6320,30 @@ } }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "hash.js": { @@ -6511,30 +6410,6 @@ "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "hsl-regex": { @@ -6571,9 +6446,9 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "html-minifier-terser": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.0.5.tgz", - "integrity": "sha512-cBSFFghQh/uHcfSiL42KxxIRMF7A144+3E44xdlctIjxEmkEfCvouxNyFH2wysXk1fCGBPwtcr3hDWlGTfkDew==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", "requires": { "camel-case": "^4.1.1", "clean-css": "^4.2.3", @@ -6582,13 +6457,6 @@ "param-case": "^3.0.3", "relateurl": "^0.2.7", "terser": "^4.6.3" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - } } }, "html-webpack-plugin": { @@ -6632,6 +6500,16 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } } } }, @@ -6660,9 +6538,9 @@ } }, "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" }, "http-proxy": { "version": "1.18.1", @@ -6823,25 +6701,30 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.2.tgz", + "integrity": "sha512-DF4osh1FM6l0RJc5YWYhSDB6TawiBRlbV9Cox8MWlidU218Tb7fm3lQTULyUJDfJ0tjbzl0W4q651mrCCEM55w==", "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", + "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.16", "mute-stream": "0.0.8", "run-async": "^2.4.0", - "rxjs": "^6.5.3", + "rxjs": "^6.6.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -6852,9 +6735,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6873,11 +6756,31 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -6954,6 +6857,16 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-arguments": { @@ -6967,11 +6880,11 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "requires": { - "binary-extensions": "^2.0.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { @@ -6980,9 +6893,9 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" }, "is-ci": { "version": "2.0.0", @@ -7011,6 +6924,16 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-date-object": { @@ -7061,9 +6984,12 @@ "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-generator-fn": { "version": "2.1.0", @@ -7084,6 +7010,16 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-obj": { @@ -7125,17 +7061,12 @@ "isobject": "^3.0.1" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", "requires": { - "has": "^1.0.3" + "has-symbols": "^1.0.1" } }, "is-regexp": { @@ -7236,6 +7167,13 @@ "@babel/types": "^7.4.0", "istanbul-lib-coverage": "^2.0.5", "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "istanbul-lib-report": { @@ -7270,6 +7208,19 @@ "source-map": "^0.6.1" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7504,491 +7455,6 @@ "micromatch": "^3.1.10", "sane": "^4.0.3", "walker": "^1.0.7" - }, - "dependencies": { - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } - } } }, "jest-jasmine2": { @@ -8058,9 +7524,9 @@ } }, "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" }, "jest-regex-util": { "version": "24.9.0", @@ -8143,6 +7609,13 @@ "slash": "^2.0.0", "strip-bom": "^3.0.0", "yargs": "^13.3.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } } }, "jest-serializer": { @@ -8168,6 +7641,13 @@ "natural-compare": "^1.4.0", "pretty-format": "^24.9.0", "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "jest-util": { @@ -8212,6 +7692,13 @@ "jest-get-type": "^24.9.0", "leven": "^3.1.0", "pretty-format": "^24.9.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } } }, "jest-watch-typeahead": { @@ -8241,6 +7728,14 @@ "astral-regex": "^1.0.0", "strip-ansi": "^5.2.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -8295,9 +7790,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -8424,11 +7919,11 @@ } }, "jsx-ast-utils": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", - "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", "object.assign": "^4.1.0" } }, @@ -8443,12 +7938,9 @@ "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "kleur": { "version": "3.0.3", @@ -8510,14 +8002,22 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } } }, "loader-fs-cache": { @@ -8539,23 +8039,6 @@ "pkg-dir": "^1.0.0" } }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, "pkg-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", @@ -8592,12 +8075,19 @@ } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } } }, "lodash": { @@ -8673,18 +8163,12 @@ } }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { - "yallist": "^3.0.2" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "make-dir": { @@ -8694,18 +8178,6 @@ "requires": { "pify": "^4.0.1", "semver": "^5.6.0" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "makeerror": { @@ -8784,30 +8256,6 @@ "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "meow": { @@ -8827,15 +8275,6 @@ "trim-newlines": "^1.0.0" }, "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -8844,68 +8283,6 @@ "repeating": "^2.0.0" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -8915,14 +8292,6 @@ "strip-indent": "^1.0.1" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", @@ -8941,6 +8310,16 @@ "arr-union": "^3.1.0", "clone-deep": "^0.2.4", "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "merge-descriptors": { @@ -8954,9 +8333,9 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "methods": { "version": "1.1.2", @@ -8986,13 +8365,6 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - } } }, "miller-rabin": { @@ -9002,24 +8374,31 @@ "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "mimic-fn": { @@ -9028,18 +8407,17 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "min-indent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", - "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, "mini-create-react-context": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", - "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", "requires": { - "@babel/runtime": "^7.4.0", - "gud": "^1.0.0", - "tiny-warning": "^1.0.2" + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" } }, "mini-css-extract-plugin": { @@ -9089,11 +8467,18 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", "requires": { "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } } }, "minipass-collect": { @@ -9113,9 +8498,9 @@ } }, "minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz", + "integrity": "sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ==", "requires": { "minipass": "^3.0.0" } @@ -9194,9 +8579,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multicast-dns": { "version": "6.2.3", @@ -9238,13 +8623,6 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - } } }, "natural-compare": { @@ -9258,9 +8636,9 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "next-tick": { "version": "1.0.0", @@ -9347,48 +8725,22 @@ "vm-browserify": "^1.0.1" }, "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "requires": { "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } } } } @@ -9408,19 +8760,12 @@ "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "node-releases": { - "version": "1.1.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", - "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==" + "version": "1.1.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", + "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==" }, "node-sass": { "version": "4.14.1", @@ -9446,11 +8791,6 @@ "true-case-path": "^1.0.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -9468,41 +8808,10 @@ "supports-color": "^2.0.0" } }, - "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -9523,22 +8832,12 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -9630,6 +8929,14 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } } } }, @@ -9639,9 +8946,9 @@ "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" }, "object-is": { "version": "1.1.2", @@ -9682,13 +8989,12 @@ } }, "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.17.5", "has": "^1.0.3" } }, @@ -9766,18 +9072,21 @@ } }, "open": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz", - "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz", + "integrity": "sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ==", "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" }, "dependencies": { "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } } } }, @@ -9877,19 +9186,19 @@ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-map": { @@ -9914,9 +9223,9 @@ } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pako": { "version": "1.0.11", @@ -9931,30 +9240,6 @@ "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "param-case": { @@ -9995,12 +9280,11 @@ } }, "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "error-ex": "^1.2.0" } }, "parse5": { @@ -10038,9 +9322,12 @@ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } }, "path-is-absolute": { "version": "1.0.1", @@ -10063,22 +9350,41 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "pify": "^3.0.0" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } } }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -10098,9 +9404,9 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pinkie": { "version": "2.0.4", @@ -10138,45 +9444,25 @@ "requires": { "locate-path": "^3.0.0" } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" } } }, "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", "requires": { - "find-up": "^2.1.0" + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + } } }, "pn": { @@ -10193,9 +9479,9 @@ } }, "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", + "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", "requires": { "async": "^2.6.2", "debug": "^3.1.1", @@ -10209,6 +9495,11 @@ "requires": { "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -10218,9 +9509,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -11128,9 +10419,9 @@ } }, "postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" }, "postcss-values-parser": { "version": "2.0.1", @@ -11175,20 +10466,8 @@ "ansi-regex": "^4.0.0", "ansi-styles": "^3.2.0", "react-is": "^16.8.4" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - } } }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -11271,6 +10550,13 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } } }, "pump": { @@ -11314,9 +10600,9 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, "query-string": { "version": "4.3.4", @@ -11444,10 +10730,18 @@ "text-table": "0.2.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "browserslist": { "version": "4.10.0", @@ -11460,6 +10754,11 @@ "pkg-up": "^3.1.0" } }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + }, "cross-spawn": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", @@ -11470,6 +10769,11 @@ "which": "^2.0.1" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -11487,29 +10791,6 @@ "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" - }, - "dependencies": { - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - } } }, "immer": { @@ -11537,6 +10818,11 @@ "through": "^2.3.6" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -11547,6 +10833,11 @@ } } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -11566,58 +10857,31 @@ } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" + "p-locate": "^4.1.0" } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "requires": { - "p-limit": "^2.0.0" + "p-limit": "^2.2.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - } - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11631,19 +10895,22 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { "ansi-regex": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - } } }, "which": { @@ -11690,47 +10957,32 @@ } }, "react-router": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", - "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.3.0", + "mini-create-react-context": "^0.4.0", "path-to-regexp": "^1.7.0", "prop-types": "^15.6.2", "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - } } }, "react-router-dom": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", - "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.1.2", + "react-router": "5.2.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -11793,83 +11045,75 @@ "webpack-dev-server": "3.10.3", "webpack-manifest-plugin": "2.2.0", "workbox-webpack-plugin": "4.3.1" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "optional": true + }, + "resolve": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", + "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==", "requires": { - "locate-path": "^3.0.0" + "path-parse": "^1.0.6" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "requires": { - "picomatch": "^2.0.7" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, "realpath-native": { @@ -11933,9 +11177,9 @@ "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==" }, "regenerate-unicode-properties": { "version": "8.2.0", @@ -11951,12 +11195,11 @@ "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "^7.8.4" } }, "regex-not": { @@ -12001,9 +11244,9 @@ } }, "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "regjsparser": { "version": "0.6.4", @@ -12042,11 +11285,6 @@ "utila": "^0.4.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -12071,14 +11309,6 @@ "dom-serializer": "0", "domelementtype": "1" } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } } } }, @@ -12125,6 +11355,23 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } } }, "request-promise-core": { @@ -12161,9 +11408,9 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", - "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "requires": { "path-parse": "^1.0.6" } @@ -12208,6 +11455,11 @@ "source-map": "0.6.1" }, "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -12307,9 +11559,9 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { "glob": "^7.1.3" } @@ -12329,12 +11581,9 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" }, "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "run-queue": { "version": "1.0.3", @@ -12345,9 +11594,9 @@ } }, "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", "requires": { "tslib": "^1.9.0" } @@ -12424,10 +11673,10 @@ "shallow-clone": "^3.0.0" } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "shallow-clone": { "version": "3.0.1", @@ -12462,11 +11711,12 @@ } }, "schema-utils": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", - "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", "requires": { - "ajv": "^6.12.0", + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", "ajv-keywords": "^3.4.1" } }, @@ -12503,9 +11753,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "send": { "version": "0.17.1", @@ -12527,26 +11777,6 @@ "statuses": "~1.5.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -12573,14 +11803,6 @@ "parseurl": "~1.3.2" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -12597,11 +11819,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -12785,14 +12002,6 @@ "use": "^3.1.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -12808,11 +12017,6 @@ "requires": { "is-extendable": "^0.1.0" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -12859,11 +12063,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -12873,6 +12072,16 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "requires": { "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "sockjs": { @@ -12912,6 +12121,11 @@ "requires": { "websocket-driver": ">=0.5.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -12946,9 +12160,9 @@ } }, "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -12967,23 +12181,23 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -13004,6 +12218,21 @@ "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "spdy-transport": { @@ -13017,12 +12246,37 @@ "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "spectre.css": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/spectre.css/-/spectre.css-0.5.8.tgz", - "integrity": "sha512-3N4WocWY+Dl6b3e5v3nsZYyp+VSDcBfGDzyyHw/H78ie9BoAhHkxmrhLxo9y8RadxYzVrPjfPdlev3hXEUzR2w==" + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/spectre.css/-/spectre.css-0.5.9.tgz", + "integrity": "sha512-9jUqwZmCnvflrxFGcK+ize43TvjwDjqMwZPVubEtSIHzvinH0TBUESm1LcOJx3Ur7bdPaeOHQIjOqBl1Y5kLFw==" }, "split-string": { "version": "3.1.0", @@ -13102,30 +12356,6 @@ "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", "requires": { "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stealthy-require": { @@ -13140,30 +12370,6 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-each": { @@ -13185,30 +12391,6 @@ "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-shift": { @@ -13246,23 +12428,13 @@ } }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - } + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string.prototype.matchall": { @@ -13287,26 +12459,6 @@ "es-abstract": "^1.17.5" } }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, "string.prototype.trimstart": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", @@ -13317,18 +12469,11 @@ } }, "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - } + "safe-buffer": "~5.1.0" } }, "stringify-object": { @@ -13349,24 +12494,27 @@ } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^2.0.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" } } }, "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } }, "strip-comments": { "version": "1.0.2", @@ -13391,9 +12539,9 @@ } }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, "style-loader": { "version": "0.23.1", @@ -13463,32 +12611,10 @@ "ms": "^2.1.1" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -13546,11 +12672,6 @@ "string-width": "^3.0.0" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -13565,6 +12686,14 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -13584,15 +12713,20 @@ } }, "terser": { - "version": "4.6.11", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.11.tgz", - "integrity": "sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", "requires": { "commander": "^2.20.0", "source-map": "~0.6.1", "source-map-support": "~0.5.12" }, "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13641,9 +12775,9 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "jest-worker": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.6.tgz", - "integrity": "sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", "requires": { "merge-stream": "^2.0.0", "supports-color": "^7.0.0" @@ -13658,21 +12792,13 @@ } }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "requires": { "semver": "^6.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -13681,11 +12807,6 @@ "p-limit": "^2.2.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -13699,6 +12820,11 @@ "find-up": "^4.0.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13723,6 +12849,73 @@ "minimatch": "^3.0.4", "read-pkg-up": "^4.0.0", "require-main-filename": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } } }, "text-table": { @@ -13747,30 +12940,6 @@ "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "thunky": { @@ -13830,6 +12999,16 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { @@ -13893,9 +13072,9 @@ "integrity": "sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==" }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, "tsutils": { "version": "3.17.1", @@ -14109,6 +13288,13 @@ "loader-utils": "^1.2.3", "mime": "^2.4.4", "schema-utils": "^2.5.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } } }, "url-parse": { @@ -14172,9 +13358,9 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==" }, "validate-npm-package-license": { "version": "3.0.4", @@ -14247,588 +13433,125 @@ } }, "watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", + "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", "requires": { - "chokidar": "^2.1.8", + "chokidar": "^3.4.0", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" }, "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } }, "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "optional": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" } }, "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "optional": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "^2.0.0" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "optional": true }, "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "optional": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "picomatch": "^2.2.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "is-number": "^7.0.0" } } } }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "optional": true, + "requires": { + "chokidar": "^2.1.8" + } + }, "wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -14908,6 +13631,14 @@ "estraverse": "^4.1.1" } }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -14918,6 +13649,14 @@ "ajv-keywords": "^3.1.0" } }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14932,20 +13671,25 @@ } }, "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", + "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^3.1.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -14959,6 +13703,13 @@ "mkdirp": "^0.5.1", "range-parser": "^1.2.1", "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } } }, "webpack-dev-server": { @@ -15002,33 +13753,14 @@ }, "dependencies": { "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "cliui": { "version": "4.1.0", @@ -15040,11 +13772,6 @@ "wrap-ansi": "^2.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -15055,6 +13782,14 @@ } } }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -15063,589 +13798,25 @@ "locate-path": "^3.0.0" } }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } - } - }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, "is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==" }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "requires": { - "binary-extensions": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "require-main-filename": { "version": "1.0.1", @@ -15662,6 +13833,11 @@ "ajv-keywords": "^3.1.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -15671,11 +13847,6 @@ "strip-ansi": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -15686,22 +13857,6 @@ } } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -15826,19 +13981,19 @@ } }, "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", + "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, "whatwg-encoding": { "version": "1.0.5", @@ -15849,9 +14004,9 @@ } }, "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz", + "integrity": "sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w==" }, "whatwg-mimetype": { "version": "2.3.0", @@ -15887,35 +14042,6 @@ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } } }, "word-wrap": { @@ -16110,11 +14236,6 @@ "strip-ansi": "^5.0.0" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -16129,6 +14250,14 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -16192,17 +14321,14 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yaml": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.0.tgz", - "integrity": "sha512-3GLZOj8A9Gsp0Fw3kOyj0zqk4xMq+YvhbHSDYALd2NMOfIpyZeBhz32ZiNU7AtX1MtXX/9JJgxSElGRwvv9enA==", - "requires": { - "@babel/runtime": "^7.9.0" - } + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" }, "yargs": { "version": "13.3.2", @@ -16221,11 +14347,6 @@ "yargs-parser": "^13.1.2" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -16239,36 +14360,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -16278,6 +14369,14 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -16288,6 +14387,13 @@ "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } } } } diff --git a/web/fling/src/components/admin/FlingContent.jsx b/web/fling/src/components/admin/FlingContent.jsx index e39a539..ad32267 100644 --- a/web/fling/src/components/admin/FlingContent.jsx +++ b/web/fling/src/components/admin/FlingContent.jsx @@ -1,42 +1,65 @@ import log from 'loglevel'; import React from 'react'; -import {Switch, Route, useLocation, Link} from "react-router-dom"; +import { Switch, Route, useLocation, Link } from "react-router-dom"; + +import { useSelector } from "react-redux"; import FlingArtifacts from './FlingArtifacts'; import Upload from './Upload'; import Settings from './Settings'; -export default function FlingContent(props) { - let location = useLocation(); +export default function FlingContent() { + const location = useLocation(); + const activeFling = useSelector(state => state.flings.activeFling); + function Empty() { + return ( +
    +
    + +
    +

    No Fling selected

    +

    Select a fling from the list

    +
    + ); + } + + function Content() { function path(tail) { - return `/admin/${props.activeFling}/${tail}`; + return `/admin/${activeFling.id}/${tail}`; } - return( -
    - {log.info("FlingContent location ", location)} - {log.info("FlingContent active fling ", props.activeFling)} -
      -
    • - Files -
    • -
    • - Upload -
    • -
    • - Settings -
    • -
    + return ( +
    + {log.info("FlingContent location ", location)} + {log.info("FlingContent active fling ", activeFling)} +
      +
    • + Files +
    • +
    • + Upload +
    • +
    • + Settings +
    • +
    -
    - - - - - - -
    +
    + + + + + +
    +
    ); + } + + return ( + <> + { activeFling ? Content() : Empty() } + + ); } diff --git a/web/fling/src/components/admin/FlingTile.jsx b/web/fling/src/components/admin/FlingTile.jsx index 4a02862..1f309ae 100644 --- a/web/fling/src/components/admin/FlingTile.jsx +++ b/web/fling/src/components/admin/FlingTile.jsx @@ -43,7 +43,7 @@ function TileAction(props) {
  • diff --git a/web/fling/src/components/admin/Login.jsx b/web/fling/src/components/admin/Login.jsx index 3599e22..98f7ad2 100644 --- a/web/fling/src/components/admin/Login.jsx +++ b/web/fling/src/components/admin/Login.jsx @@ -1,5 +1,5 @@ import log from 'loglevel'; -import React, {useState} from 'react'; +import React, {useState, useEffect} from 'react'; import {useHistory, useLocation} from 'react-router-dom'; import {fc, AuthClient} from '../../util/fc'; @@ -11,6 +11,25 @@ export default function Login() { const location = useLocation(); const { from } = location.state || { from: { pathname: "/admin" } }; + useEffect(() => { + sessionStorage.removeItem("token") + }); + + function handleSubmit(ev) { + ev.preventDefault(); + + let authClient = new AuthClient(); + let opt = {adminAuth: new fc.AdminAuth(username, password)}; + + authClient.authenticateOwner(opt) + .then(response => { + log.info("Login successful"); + sessionStorage.setItem('token', response); + log.debug("Returning back to", from); + history.replace(from); + }).catch(log.error); + }; + return (
    @@ -41,19 +60,4 @@ export default function Login() {
    ); - - function handleSubmit(ev) { - ev.preventDefault(); - - let authClient = new AuthClient(); - let opt = {adminAuth: new fc.AdminAuth(username, password)}; - - authClient.authenticateOwner(opt) - .then(response => { - log.info("Login successful"); - sessionStorage.setItem('token', response); - log.debug("Returning back to", from); - history.replace(from); - }).catch(log.error); - }; } diff --git a/web/fling/src/components/admin/Upload.jsx b/web/fling/src/components/admin/Upload.jsx index edf034e..2996c46 100644 --- a/web/fling/src/components/admin/Upload.jsx +++ b/web/fling/src/components/admin/Upload.jsx @@ -1,217 +1,208 @@ import log from 'loglevel'; -import React, {useState, useEffect, useRef} from 'react'; +import React, { useState, useEffect, useRef } from 'react'; +import { useSelector } from "react-redux"; -import {artifactClient} from '../../util/flingclient'; +import { ArtifactClient, FlingClient, fc } from '../../util/fc'; +import { prettifyBytes, prettifyTimestamp } from '../../util/fn'; import upload from '../resources/upload.svg'; import drop from '../resources/drop.svg'; +export default function Upload() { + let fileInputRef = useRef(null); + let [files, setFiles] = useState([]); + let [dragging, setDragging] = useState(false); + let [dragCount, setDragCount] = useState(0); -export default function Upload(props) { - let fileInputRef = useRef(null); - let [files, setFiles] = useState([]); - let [dragging, setDragging] = useState(false); - let [dragCount, setDragCount] = useState(0); + const activeFling = useSelector(state => state.flings.activeFling); - useEffect(() => { - // prevent browser from trying to open the file when drag event - // not recognized properly - window.addEventListener("dragover",function(e){ - e.preventDefault(); - },false); - window.addEventListener("drop",function(e){ - e.preventDefault(); - },false); - }); + useEffect(() => { + // prevent browser from trying to open the file when drag event not + // recognized properly + window.addEventListener("dragover", e => e.preventDefault(), false); + window.addEventListener("drop", e => e.preventDefault(), false); + }); - function fileList() { - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; - - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } - - let fileList = []; - files.forEach((file,idx) => { - if(!file.uploaded) { - fileList.push( -
    -
    -
    - deleteFile(idx)}/> -
    {file.name}
    -
    {(new Date(file.lastModified)).toLocaleString()+", "+readableBytes(file.size)}
    -
    -
    -
    - ); - } - }); - - return fileList; - } - - function deleteFile(idx) { - let f = [...files]; - f.splice(idx, 1); - setFiles(f); - } - - function totalSize() { - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; - - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } - - let totalSize = 0; - for(let file of files) { - totalSize += file.size; - } - - return readableBytes(totalSize); - } - - function handleClick(ev) { - fileInputRef.current.click(); - } - - function handleFileInputChange(ev) { - let fileInputFiles = fileInputRef.current.files; - if (!fileInputFiles) { - console.warn("No files selected"); - return; - } - - setFiles([...files, ...fileInputFiles]); - } - - function handleDrop(ev) { - stopEvent(ev); - ev.persist(); - - let evFiles = ev.dataTransfer.files; - - if (!evFiles) { - console.warn("Dropzone triggered without files"); - return; - } - - setFiles([...files, ...fileListToArray(evFiles)]); - setDragging(false); - setDragCount(0); - } - - function fileListToArray(fileList) { - if(fileList === undefined || fileList === null) { - return []; - } - - let arr = []; - for (let i=0; i i.name).join(',')+"]"); - } - - function setFileUploaded(idx) { - let f = [...files]; - f[idx].uploaded = true; - setFiles(f); - } - - function handleUpload() { - files.forEach((file, idx) => { - artifactClient.postArtifact(props.activeFling, file) - .then(response => { - setFileUploaded(idx); - }); - }); - } - - function zoneContent(dragging) { - if(dragging){ - return( - <> - dropzone icon -
    Drop now!
    - - ); - }else { - return( - <> - dropzone icon -
    Click or Drop
    - - ); - } - } - - return( -
    - {logFiles()} -
    -
    -
    - - - {zoneContent(dragging)} - -
    -
    - -
    -
    -
    -
    -
    - {fileList()} -
    -
    -
    -
    - Total Size: {totalSize()} - + function fileList() { + let fileList = []; + files.forEach((file, idx) => { + if (!file.uploaded) { + fileList.push( +
    +
    +
    + deleteFile(idx)} /> +
    {file.name}
    +
    + {`${prettifyTimestamp(file.lastModified)}, ` + + `${prettifyBytes(file.size)}`}
    + ); + } + }); + + return fileList; + } + + function deleteFile(idx) { + let f = [...files]; + f.splice(idx, 1); + setFiles(f); + } + + function totalSize() { + let totalSize = 0; + for (let file of files) { + totalSize += file.size; + } + + return prettifyBytes(totalSize); + } + + function handleClick(ev) { + fileInputRef.current.click(); + } + + function handleFileInputChange(ev) { + let fileInputFiles = fileInputRef.current.files; + if (!fileInputFiles) { + console.warn("No files selected"); + return; + } + + setFiles([...files, ...fileInputFiles]); + } + + function handleDrop(ev) { + stopEvent(ev); + ev.persist(); + + let evFiles = ev.dataTransfer.files; + + if (!evFiles) { + console.warn("Dropzone triggered without files"); + return; + } + + setFiles([...files, ...fileListToArray(evFiles)]); + setDragging(false); + setDragCount(0); + } + + function fileListToArray(fileList) { + if (fileList === undefined || fileList === null) { + return []; + } + + let arr = []; + for (let i = 0; i < fileList.length; i++) { arr.push(fileList[i]); } + + return arr; + } + + function handleOnDragEnter(ev) { + stopEvent(ev); + if (dragCount === 0) setDragging(true); + + setDragCount(dragCount + 1); + } + + function handleOnDragLeave(ev) { + stopEvent(ev); + let dc = dragCount; + + dc -= 1; + setDragCount(dc); + + if (dc === 0) setDragging(false); + } + + function stopEvent(ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + function logFiles() { + log.info("Files so far: [" + files.map((i) => i.name).join(',') + "]"); + } + + function setFileUploaded(idx) { + let f = [...files]; + f[idx].uploaded = true; + setFiles(f); + } + + function handleUpload() { + const flingClient = new FlingClient(); + const artifactClient = new ArtifactClient(); + + files.forEach((file, idx) => { + let artifact = new fc.Artifact(file.name) + + flingClient.postArtifact(activeFling.id, { artifact: artifact }) + .then(artifact => { + artifactClient.uploadArtifactData(artifact.id, { body: file }); + setFileUploaded(idx); + }); + }); + } + + function zoneContent(dragging) { + if (dragging) { + return ( + <> + dropzone icon +
    Drop now!
    + + ); + } else { + return ( + <> + dropzone icon +
    Click or Drop
    + + ); + } + } + + return ( +
    + {logFiles()} +
    +
    +
    + + + {zoneContent(dragging)} + +
    - ); + +
    +
    +
    +
    +
    + {fileList()} +
    +
    +
    +
    + Total Size: {totalSize()} + +
    +
    +
    +
    +
    + ); } diff --git a/web/fling/src/redux/reducers/artifacts.js b/web/fling/src/redux/reducers/artifacts.js new file mode 100644 index 0000000..8a28cb2 --- /dev/null +++ b/web/fling/src/redux/reducers/artifacts.js @@ -0,0 +1,39 @@ +import log from "loglevel"; +import produce from "immer"; + +import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "../actionTypes"; + +const initialState = { + // type [fc.Artifact] + aritfacts: [] +} + +export default produce((draft, action) => { + switch (action.type) { + case SET_FLINGS: + draft.flings = action.payload; + break; + case ADD_FLING: + // Check storage again here, otherwise there could be a race + // condition due to async calls of SET_FLINGS and ADD_FLING + let foundFlingIdx = draft.flings.findIndex(fling => + fling.id === action.payload.id); + + if (foundFlingIdx === -1) { + log.debug(`Adding new fling with id ${action.payload.id}`) + draft.flings.push(action.payload); + } else { + log.debug(`Fling already exists. ` + + `Updating fling with id ${action.payload.id}`) + draft.flings[foundFlingIdx] = action.payload + } + break; + case SET_ACTIVE_FLING: + draft.activeFling = action.payload; + break; + default: + break; + } + return draft; + +}, initialState); diff --git a/web/fling/src/util/fn.js b/web/fling/src/util/fn.js new file mode 100644 index 0000000..146a7e5 --- /dev/null +++ b/web/fling/src/util/fn.js @@ -0,0 +1,19 @@ +/* + * Returns a human readable presentation of `bytes` + */ +export function prettifyBytes(bytes) { + if (bytes <= 0) return "0 KB"; + + var i = Math.floor(Math.log(bytes) / Math.log(1024)), + sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; +} + +/** + * Returns a human readable date for a unix timestamp in milliseconds + */ +export function prettifyTimestamp(timestamp, withTime=false) { + let date = new Date(timestamp); + return withTime ? date.toLocaleString(): date.toLocaleDateString(); +} From c07a7866ce72e2cf9e62e37e8e82cb5fe0dd8b56 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 20 Jul 2020 00:27:02 +0200 Subject: [PATCH 22/30] New Fling with new API Only a crude implementation for now --- .../security/FlingWebSecurityConfigurer.java | 8 +- .../fling/service/AuthorizationService.java | 5 + web/fling/src/components/admin/New.jsx | 475 +++++++++--------- 3 files changed, 259 insertions(+), 229 deletions(-) diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index e5a0d0f..180d2cf 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -77,15 +77,15 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { /***********************************/ /** Authorization for: /api/fling **/ /***********************************/ - .authorizeRequests() - .antMatchers(HttpMethod.GET, "/api/fling/{flingId}/**") - .access("@authorizationService.allowFlingAccess(#flingId, authentication)") - .and() .authorizeRequests() .antMatchers(HttpMethod.GET, "/api/fling/share/{shareId}") .access("@authorizationService.allowFlingAccessByShareId(#shareId, authentication)") .and() .authorizeRequests() + .antMatchers(HttpMethod.GET, "/api/fling/{flingId}/**") + .access("@authorizationService.allowFlingAccess(#flingId, authentication)") + .and() + .authorizeRequests() .antMatchers(HttpMethod.POST, "/api/fling/{flingId}/artifact") .access("@authorizationService.allowUpload(#flingId, authentication)") .and() diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java index a405cfe..e136e31 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java @@ -69,6 +69,11 @@ public class AuthorizationService { } public boolean allowFlingAccessByShareId(String shareId, AbstractAuthenticationToken token) { + if (FlingAuthorities.FLING_ADMIN.verify(token)) { + log.debug("Owner authorized for fling access [shareId = {}]", shareId); + return true; + } + FlingEntity flingEntity = flingRepository.findByShareId(shareId); if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+shareId); } return allowFlingAccess(flingEntity.getId(), token); diff --git a/web/fling/src/components/admin/New.jsx b/web/fling/src/components/admin/New.jsx index 7a8fb30..2f3b43a 100644 --- a/web/fling/src/components/admin/New.jsx +++ b/web/fling/src/components/admin/New.jsx @@ -1,242 +1,267 @@ import log from 'loglevel'; -import React, {useState} from 'react'; +import React, { useState } from 'react'; -import {flingClient} from '../../util/flingclient'; +import { FlingClient, fc } from '../../util/fc'; export default function New(props) { - let defaultState = () => ({name: "", authCode: "", - sharing: {directDownload: true, allowUpload: false, shared: true, shareUrl: ""}, - expiration: {}}); + let defaultState = () => ({ + name: "", authCode: "", + sharing: { directDownload: true, allowUpload: false, shared: true, shareUrl: "" }, + expiration: {} + }); - let [fling, setFling] = useState(defaultState()); - let [shareUrlUnique, setShareUrlUnique] = useState(true); + let [fling, setFling] = useState(defaultState()); + let [shareUrlUnique, setShareUrlUnique] = useState(true); - function toggleSharing(ev) { - let f = {...fling}; - let s = {...fling.sharing}; + function toggleSharing(ev) { + let f = { ...fling }; + let s = { ...fling.sharing }; - if(ev.currentTarget.id === "direct-download") { - if(ev.currentTarget.checked) { - s.directDownload = true; - s.shared = true; - s.allowUpload = false; - } else { - s.directDownload = false; - } - } else if(ev.currentTarget.id === "allow-upload") { - if(ev.currentTarget.checked) { - s.allowUpload = true; - s.shared = true; - s.directDownload = false; - } else { - s.allowUpload = false; - } - } else if(ev.currentTarget.id === "shared") { - if(!ev.currentTarget.checked) { - s.allowUpload = s.directDownload = s.shared = false; - } else { - s.shared = true; - } + if (ev.currentTarget.id === "direct-download") { + if (ev.currentTarget.checked) { + s.directDownload = true; + s.shared = true; + s.allowUpload = false; + } else { + s.directDownload = false; + } + } else if (ev.currentTarget.id === "allow-upload") { + if (ev.currentTarget.checked) { + s.allowUpload = true; + s.shared = true; + s.directDownload = false; + } else { + s.allowUpload = false; + } + } else if (ev.currentTarget.id === "shared") { + if (!ev.currentTarget.checked) { + s.allowUpload = s.directDownload = s.shared = false; + } else { + s.shared = true; + } + } + + f.sharing = s; + + setFling(f); + } + + function handleClose(ev) { + if (ev) ev.preventDefault(); // this is needed, otherwise a submit event is fired + props.closeModalFn(); + } + + function setShareUrl(ev) { + let f = { ...fling }; + let s = { ...fling.sharing }; //TODO: expiration is not cloned + let value = ev.currentTarget.value; + + if (!value) { + setShareUrlUnique(false); + s.shareUrl = value; + f.sharing = s; + setFling(f); + return; + } + + const flingClient = new FlingClient(); + flingClient.getFlingByShareId(ev.currentTarget.value) + .then(result => { + setShareUrlUnique(false); + }).catch(error => { + if(error.status === 404) { + setShareUrlUnique(true); } - + }).finally(() => { + s.shareUrl = value; f.sharing = s; - setFling(f); + }); + } + + function setName(ev) { + let f = { ...fling }; + let value = ev.currentTarget.value; + + f.name = value; + setFling(f); + } + + function setExpirationType(ev) { + let f = { ...fling }; + let e = { ...fling.expiration }; //TODO: sharing is not cloned + let value = ev.currentTarget.value; + + if (value === "never") { + e = {}; + } else { + e.type = value; + e.value = ""; } - function handleClose(ev) { - if(ev) ev.preventDefault(); // this is needed, otherwise a submit event is fired - props.closeModalFn(); + f.expiration = e; + setFling(f); + } + + function setExpirationValue(ev) { + let f = { ...fling }; + let e = { ...fling.expiration }; //TODO: sharing is not cloned + let value = e.type === "time" ? ev.currentTarget.valueAsNumber : ev.currentTarget.value; + + e.value = value; + + f.expiration = e; + setFling(f); + } + + function formatExpirationTime() { + if (!fling.expiration || !fling.expiration.value || fling.expiration.type !== "time") + return ""; + + + let date = new Date(fling.expiration.value); + let fmt = date.toISOString().split("T")[0]; + return fmt; + } + + function setAuthCode(ev) { + let f = { ...fling }; + let value = ev.currentTarget.value; + + f.authCode = value; + setFling(f); + } + + function handleSubmit(ev) { + ev.preventDefault(); + log.info("Creating new filing"); + const flingClient = new FlingClient(); + + let flingEntity = new fc.Fling(fling.name); + flingEntity.directDownload = fling.sharing.directDownload; + flingEntity.allowUpload = fling.sharing.allowUpload; + flingEntity.shared = fling.sharing.shared; + flingEntity.shareId = fling.sharing.shareUrl; + flingEntity.authCode = fling.authCode; + if (fling.expiration.type) { + switch (fling.expiration.type) { + case "time": + flingEntity.expirationTime = fling.expiration.value; + break; + case "clicks": + flingEntity.expirationClicks = fling.expiration.value; + break; + default: + log.warn("Unknown expiration type"); + break; + } } - function setShareUrl(ev) { - let f = {...fling}; - let s = {...fling.sharing}; //TODO: expiration is not cloned - let value = ev.currentTarget.value; + flingClient.postFling({fling: flingEntity}) + .then(() => handleClose()) + .catch(error => log.error(error)) + } - if(!value) { - setShareUrlUnique(false); - s.shareUrl = value; - f.sharing = s; - setFling(f); - return; - } - - flingClient.getFlingByShareId(ev.currentTarget.value) - .then(result => { - if(!result) { - setShareUrlUnique(true); - } else { - setShareUrlUnique(false); - } - - s.shareUrl = value; - f.sharing = s; - setFling(f); - }); - } - - function setName(ev) { - let f = {...fling}; - let value = ev.currentTarget.value; - - f.name = value; - setFling(f); - } - - function setExpirationType(ev) { - let f = {...fling}; - let e = {...fling.expiration}; //TODO: sharing is not cloned - let value = ev.currentTarget.value; - - if(value === "never") { - e = {}; - } else { - e.type = value; - e.value = ""; - } - - f.expiration = e; - setFling(f); - } - - function setExpirationValue(ev) { - let f = {...fling}; - let e = {...fling.expiration}; //TODO: sharing is not cloned - let value = e.type === "time" ? ev.currentTarget.valueAsNumber: ev.currentTarget.value; - - e.value = value; - - f.expiration = e; - setFling(f); - } - - function formatExpirationTime() { - if (!fling.expiration || !fling.expiration.value || fling.expiration.type !== "time") - return ""; - - - let date = new Date(fling.expiration.value); - let fmt = date.toISOString().split("T")[0]; - return fmt; - } - - function setAuthCode(ev) { - let f = {...fling}; - let value = ev.currentTarget.value; - - f.authCode = value; - setFling(f); - } - - function handleSubmit(ev) { - ev.preventDefault(); - log.info("Creating new filing"); - log.info(fling); - flingClient.postFling(fling); - handleClose(); - } - - return( -
    -
    -
    -
    -
    New Fling
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - - -
    -
    - -
    -
    - -
    -
    -
    - - -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    - -
    -
    - Expire after - - Clicks -
    -
    - -
    -
    - Expire after - -
    -
    -
    -
    - -
    -
    - -
    -
    - - - - -
    -
    - -
    - - -
    - -
    -
    + return ( +
    +
    +
    +
    +
    New Fling
    - ); +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + Expire after + + Clicks +
    +
    + +
    +
    + Expire after + +
    +
    +
    +
    + +
    +
    + +
    +
    + + + + +
    +
    + +
    + + +
    + +
    +
    +
    + ); } From a07379ebadcf9925520251cc8dd5282f8470304b Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Tue, 21 Jul 2020 21:24:10 +0200 Subject: [PATCH 23/30] Generate derived authentication tokens Can be used to authorize download urls via query parameter since the tokens can be used only once. --- examples/querysheet.http | 5 + .../controller/AuthenticationController.java | 25 +++ .../persistence/entities/TokenEntity.java | 38 ++++ .../repositories/TokenRepository.java | 8 + .../fling/security/FlingAuthorities.java | 2 +- .../security/FlingWebSecurityConfigurer.java | 25 ++- .../security/authentication/FlingToken.java | 26 ++- .../FlingAdminAuthority.java | 2 +- .../{ => authorities}/FlingUserAuthority.java | 2 +- .../BearerAuthenticationFilter.java} | 13 +- .../filter/TokenAuthenticationFilter.java | 59 ++++++ .../friedl/fling/service/ArtifactService.java | 2 +- .../fling/service/AuthenticationService.java | 68 ++++++- .../fling/service/AuthorizationService.java | 4 +- .../friedl/fling/service/FlingService.java | 4 +- .../AuthenticationControllerTest.java | 1 + .../service/AuthenticationServiceTest.java | 141 ++++++++++++-- .../service/AuthorizationServiceTest.java | 26 ++- .../src/components/admin/FlingArtifacts.jsx | 182 +++++++++--------- web/fling/src/redux/reducers/artifacts.js | 39 ---- 20 files changed, 483 insertions(+), 189 deletions(-) create mode 100644 service/fling/src/main/java/net/friedl/fling/persistence/entities/TokenEntity.java create mode 100644 service/fling/src/main/java/net/friedl/fling/persistence/repositories/TokenRepository.java rename service/fling/src/main/java/net/friedl/fling/security/authentication/{ => authorities}/FlingAdminAuthority.java (85%) rename service/fling/src/main/java/net/friedl/fling/security/authentication/{ => authorities}/FlingUserAuthority.java (89%) rename service/fling/src/main/java/net/friedl/fling/security/authentication/{JwtAuthenticationFilter.java => filter/BearerAuthenticationFilter.java} (79%) create mode 100644 service/fling/src/main/java/net/friedl/fling/security/authentication/filter/TokenAuthenticationFilter.java delete mode 100644 web/fling/src/redux/reducers/artifacts.js diff --git a/examples/querysheet.http b/examples/querysheet.http index a9e3a3c..cb84cd8 100644 --- a/examples/querysheet.http +++ b/examples/querysheet.http @@ -47,6 +47,11 @@ Content-Type: application/json :token {"name": "Fling from querysheet with Auth and very long name", "expirationClicks": 12, "shared": true, "authCode": "abc"} +# GET derived auth token +GET http://localhost:8080/api/auth/derive +Content-Type: application/json +:token + # :flingId = dfc208a3-5924-43b4-aa6a-c263541dca5e diff --git a/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java b/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java index ec896fe..47be787 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/AuthenticationController.java @@ -1,17 +1,23 @@ package net.friedl.fling.controller; +import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.model.dto.AdminAuthDto; import net.friedl.fling.model.dto.UserAuthDto; +import net.friedl.fling.security.FlingWebSecurityConfigurer; import net.friedl.fling.service.AuthenticationService; @RestController @@ -50,4 +56,23 @@ public class AuthenticationController { return authenticationService.authenticate(userAuthDto) .orElseThrow(() -> new AccessDeniedException("Wrong username or password")); } + + //@formatter:off + /** + * Note that this endpoint is not protected. But the token will only get authority of the + * authenticated user. + * @see FlingWebSecurityConfigurer + * @see AuthenticationService + */ + @Operation(description = "Generate a derived token from the current authorization") + @ApiResponse(responseCode = "200", description = "Token impersonating the user") + @SecurityRequirement(name = "bearer") + @GetMapping("/derive") + public String deriveToken( + @Parameter(allowEmptyValue = true, description = "Token can only be used for authorizing one request. Defaults to true") + @RequestParam Optional singleUse) + { + return authenticationService.deriveToken(singleUse.orElse(true)); + } + //@formatter:on } diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/entities/TokenEntity.java b/service/fling/src/main/java/net/friedl/fling/persistence/entities/TokenEntity.java new file mode 100644 index 0000000..33e0d98 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/persistence/entities/TokenEntity.java @@ -0,0 +1,38 @@ +package net.friedl.fling.persistence.entities; + +import java.time.Instant; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Version; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "Token") +@Getter +@Setter +public class TokenEntity { + @Id + private UUID id; // Note that this is not generated to ensure randomness independent from the + // persistence provider + + @Column(nullable = false) + private Boolean singleUse = true; + + @Column(nullable = false) + private String token; // JWT token this token is derived from + + @CreationTimestamp + private Instant creationTime; + + @UpdateTimestamp + private Instant updateTime; + + @Version + private Long version; +} diff --git a/service/fling/src/main/java/net/friedl/fling/persistence/repositories/TokenRepository.java b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/TokenRepository.java new file mode 100644 index 0000000..07837ea --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/persistence/repositories/TokenRepository.java @@ -0,0 +1,8 @@ +package net.friedl.fling.persistence.repositories; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import net.friedl.fling.persistence.entities.TokenEntity; + +public interface TokenRepository extends JpaRepository { +} diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java b/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java index 3dd4698..3742de5 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingAuthorities.java @@ -4,7 +4,7 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; public enum FlingAuthorities { - FLING_ADMIN("admin"), FLING_USER("user"); + FLING_ADMIN("admin"), FLING_USER("user"), FLING_TOKEN("token"); String authority; diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index 180d2cf..fe6ed9d 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -1,6 +1,7 @@ package net.friedl.fling.security; import static net.friedl.fling.security.FlingAuthorities.FLING_ADMIN; +import static net.friedl.fling.security.FlingAuthorities.FLING_USER; import static org.springframework.security.config.Customizer.withDefaults; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -18,8 +19,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import net.friedl.fling.FlingSecurityConfiguration; -import net.friedl.fling.security.authentication.JwtAuthenticationFilter; +import net.friedl.fling.security.authentication.filter.BearerAuthenticationFilter; +import net.friedl.fling.security.authentication.filter.TokenAuthenticationFilter; import net.friedl.fling.service.AuthorizationService; @Slf4j @@ -30,15 +31,18 @@ import net.friedl.fling.service.AuthorizationService; public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { private List allowedOrigins; - private JwtAuthenticationFilter jwtAuthenticationFilter; + private TokenAuthenticationFilter tokenAuthenticationFilter; + private BearerAuthenticationFilter bearerAuthenticationFilter; private AuthorizationService authorizationService; @Autowired - public FlingWebSecurityConfigurer(JwtAuthenticationFilter jwtAuthenticationFilter, - AuthorizationService authorizationService, - FlingSecurityConfiguration securityConfiguraiton) { + public FlingWebSecurityConfigurer( + TokenAuthenticationFilter tokenAuthenticationFilter, + BearerAuthenticationFilter bearerAuthenticationFilter, + AuthorizationService authorizationService) { - this.jwtAuthenticationFilter = jwtAuthenticationFilter; + this.tokenAuthenticationFilter = tokenAuthenticationFilter; + this.bearerAuthenticationFilter = bearerAuthenticationFilter; this.authorizationService = authorizationService; } @@ -52,7 +56,8 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { /**********************************************/ /** Authentication Interceptor Configuration **/ /**********************************************/ - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterAfter(bearerAuthenticationFilter, TokenAuthenticationFilter.class) // Do not keep authorization token in session. This would interfere with bearer authentication // in that it is possible to authenticate without a bearer token if the session is kept. // Turn off this confusing and non-obvious behavior. @@ -68,6 +73,10 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { /**********************************/ /** Authorization for: /api/auth **/ /**********************************/ + .authorizeRequests() + .antMatchers("/api/auth/derive") + .hasAnyAuthority(FLING_ADMIN.getAuthority(), FLING_USER.getAuthority()) + .and() .authorizeRequests() .antMatchers("/api/auth/**") .permitAll() diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java index e731e35..41f0565 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingToken.java @@ -4,25 +4,31 @@ import java.util.List; import java.util.UUID; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; +import net.friedl.fling.security.authentication.authorities.FlingAdminAuthority; +import net.friedl.fling.security.authentication.authorities.FlingUserAuthority; public class FlingToken extends AbstractAuthenticationToken { private static final long serialVersionUID = -1112423505610346583L; - private String jwtToken; + private String token; - public FlingToken(GrantedAuthority authority, String jwtToken) { - super(List.of(authority)); - this.jwtToken = jwtToken; + public FlingToken(List authorities, String token) { + super(authorities); + this.token = token; } public boolean authorizedForFling(UUID id) { for (GrantedAuthority grantedAuthority : getAuthorities()) { - if (grantedAuthority instanceof FlingAdminAuthority) return true; + if (grantedAuthority instanceof FlingAdminAuthority) { + return true; + } - if (!(grantedAuthority instanceof FlingUserAuthority)) continue; - - UUID grantedFlingId = ((FlingUserAuthority) grantedAuthority).getFlingId(); - if (grantedFlingId.equals(id)) return true; + if (grantedAuthority instanceof FlingUserAuthority) { + UUID grantedFlingId = ((FlingUserAuthority) grantedAuthority).getFlingId(); + if (grantedFlingId.equals(id)) { + return true; + } + } } return false; @@ -30,7 +36,7 @@ public class FlingToken extends AbstractAuthenticationToken { @Override public String getCredentials() { - return this.jwtToken; + return this.token; } @Override diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/authorities/FlingAdminAuthority.java similarity index 85% rename from service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java rename to service/fling/src/main/java/net/friedl/fling/security/authentication/authorities/FlingAdminAuthority.java index 1784d56..5251b41 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingAdminAuthority.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/authorities/FlingAdminAuthority.java @@ -1,4 +1,4 @@ -package net.friedl.fling.security.authentication; +package net.friedl.fling.security.authentication.authorities; import org.springframework.security.core.GrantedAuthority; import net.friedl.fling.security.FlingAuthorities; diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/authorities/FlingUserAuthority.java similarity index 89% rename from service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java rename to service/fling/src/main/java/net/friedl/fling/security/authentication/authorities/FlingUserAuthority.java index 566eaf1..dccd644 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/FlingUserAuthority.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/authorities/FlingUserAuthority.java @@ -1,4 +1,4 @@ -package net.friedl.fling.security.authentication; +package net.friedl.fling.security.authentication.authorities; import java.util.UUID; import org.springframework.security.core.GrantedAuthority; diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/filter/BearerAuthenticationFilter.java similarity index 79% rename from service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java rename to service/fling/src/main/java/net/friedl/fling/security/authentication/filter/BearerAuthenticationFilter.java index b4e5dc4..603178a 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/authentication/JwtAuthenticationFilter.java +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/filter/BearerAuthenticationFilter.java @@ -1,4 +1,4 @@ -package net.friedl.fling.security.authentication; +package net.friedl.fling.security.authentication.filter; import java.io.IOException; import java.util.stream.Collectors; @@ -13,18 +13,19 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.security.authentication.FlingToken; import net.friedl.fling.service.AuthenticationService; @Slf4j @Component -public class JwtAuthenticationFilter extends OncePerRequestFilter { +public class BearerAuthenticationFilter extends OncePerRequestFilter { private static final String TOKEN_PREFIX = "Bearer "; private static final String HEADER_STRING = "Authorization"; private AuthenticationService authenticationService; @Autowired - public JwtAuthenticationFilter(AuthenticationService authenticationService) { + public BearerAuthenticationFilter(AuthenticationService authenticationService) { this.authenticationService = authenticationService; } @@ -37,7 +38,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { if (header == null || !header.startsWith(TOKEN_PREFIX)) { log.info("Anonymous request for {} {}{}", request.getMethod(), request.getRequestURL(), - request.getQueryString() != null ? "?"+request.getQueryString(): ""); + request.getQueryString() != null ? "?" + request.getQueryString() : ""); filterChain.doFilter(request, response); return; } @@ -48,8 +49,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { if (securityContext.getAuthentication() == null) { log.info("Authenticating request for {} {}{}", request.getMethod(), request.getRequestURL(), - request.getQueryString() != null ? "?"+request.getQueryString(): ""); - FlingToken token = authenticationService.parseAuthentication(authToken); + request.getQueryString() != null ? "?" + request.getQueryString() : ""); + FlingToken token = authenticationService.parseJwtAuthentication(authToken); log.info("Authenticated as {}", token.getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.joining(","))); securityContext.setAuthentication(token); diff --git a/service/fling/src/main/java/net/friedl/fling/security/authentication/filter/TokenAuthenticationFilter.java b/service/fling/src/main/java/net/friedl/fling/security/authentication/filter/TokenAuthenticationFilter.java new file mode 100644 index 0000000..5650c05 --- /dev/null +++ b/service/fling/src/main/java/net/friedl/fling/security/authentication/filter/TokenAuthenticationFilter.java @@ -0,0 +1,59 @@ +package net.friedl.fling.security.authentication.filter; + +import java.io.IOException; +import java.util.stream.Collectors; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import lombok.extern.slf4j.Slf4j; +import net.friedl.fling.security.authentication.FlingToken; +import net.friedl.fling.service.AuthenticationService; + +@Slf4j +@Component +public class TokenAuthenticationFilter extends OncePerRequestFilter { + private AuthenticationService authenticationService; + + @Autowired + public TokenAuthenticationFilter(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String derivedToken = request.getParameter("derivedtoken"); + if (derivedToken == null) { + log.info("No derived token in request for {} {}{}", request.getMethod(), + request.getRequestURL(), + request.getQueryString() != null ? "?" + request.getQueryString() : ""); + + filterChain.doFilter(request, response); + return; + } + + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext.getAuthentication() == null) { + log.info("Authenticating request for {} {}{}", request.getMethod(), request.getRequestURL(), + request.getQueryString() != null ? "?" + request.getQueryString() : ""); + + FlingToken token = authenticationService.parseDerivedToken(derivedToken); + + log.info("Authenticated as {}", token.getAuthorities().stream() + .map(GrantedAuthority::getAuthority).collect(Collectors.joining(","))); + + securityContext.setAuthentication(token); + } + + filterChain.doFilter(request, response); + } + +} diff --git a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java index b839c2b..e9adf61 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/ArtifactService.java @@ -56,7 +56,7 @@ public class ArtifactService { */ public ArtifactDto create(UUID flingId, ArtifactDto artifactDto) { FlingEntity flingEntity = flingRepository.getOne(flingId); - + log.debug("Creating new ArtifactEntity for ArtifactDto[.path={}]", artifactDto.getPath()); ArtifactEntity artifactEntity = artifactMapper.map(artifactDto); artifactEntity.setFling(flingEntity); diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java index 71038aa..e15fb73 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java @@ -3,12 +3,16 @@ package net.friedl.fling.service; import java.security.Key; import java.time.Instant; import java.util.Date; +import java.util.List; import java.util.Optional; import java.util.UUID; import javax.persistence.EntityNotFoundException; +import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import io.jsonwebtoken.Claims; @@ -19,10 +23,12 @@ import lombok.extern.slf4j.Slf4j; import net.friedl.fling.model.dto.AdminAuthDto; import net.friedl.fling.model.dto.UserAuthDto; import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.entities.TokenEntity; import net.friedl.fling.persistence.repositories.FlingRepository; -import net.friedl.fling.security.authentication.FlingAdminAuthority; +import net.friedl.fling.persistence.repositories.TokenRepository; import net.friedl.fling.security.authentication.FlingToken; -import net.friedl.fling.security.authentication.FlingUserAuthority; +import net.friedl.fling.security.authentication.authorities.FlingAdminAuthority; +import net.friedl.fling.security.authentication.authorities.FlingUserAuthority; @Slf4j @Service @@ -30,6 +36,7 @@ public class AuthenticationService { private JwtParser jwtParser; private Key jwtSigningKey; private FlingRepository flingRepository; + private TokenRepository tokenRepository; private PasswordEncoder passwordEncoder; @Value("${fling.security.admin-name}") @@ -41,12 +48,14 @@ public class AuthenticationService { @Autowired public AuthenticationService(JwtParser jwtParser, Key jwtSigningKey, - PasswordEncoder passwordEncoder, FlingRepository flingRepository) { + PasswordEncoder passwordEncoder, FlingRepository flingRepository, + TokenRepository tokenRepository) { this.jwtParser = jwtParser; this.jwtSigningKey = jwtSigningKey; this.passwordEncoder = passwordEncoder; this.flingRepository = flingRepository; + this.tokenRepository = tokenRepository; } public Optional authenticate(AdminAuthDto adminAuth) { @@ -71,7 +80,9 @@ public class AuthenticationService { public Optional authenticate(UserAuthDto userAuth) { log.info("Authenticating for fling [.shareId={}]", userAuth.getShareId()); FlingEntity flingEntity = flingRepository.findByShareId(userAuth.getShareId()); - if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+userAuth.getShareId()); } + if (flingEntity == null) { + throw new EntityNotFoundException("No entity for shareId=" + userAuth.getShareId()); + } String providedAuthCodeHash = passwordEncoder.encode(userAuth.getAuthCode()); String actualAuthCodeHash = flingEntity.getAuthCode(); @@ -90,15 +101,15 @@ public class AuthenticationService { } - public FlingToken parseAuthentication(String token) { + public FlingToken parseJwtAuthentication(String token) { Claims claims = jwtParser.parseClaimsJws(token).getBody(); switch (claims.getSubject()) { case "admin": - return new FlingToken(new FlingAdminAuthority(), token); + return new FlingToken(List.of(new FlingAdminAuthority()), token); case "user": UUID grantedFlingId = UUID.fromString(claims.get("id", String.class)); - return new FlingToken(new FlingUserAuthority(grantedFlingId), token); + return new FlingToken(List.of(new FlingUserAuthority(grantedFlingId)), token); default: throw new BadCredentialsException("Invalid token"); } @@ -116,4 +127,47 @@ public class AuthenticationService { .setExpiration(Date.from(Instant.now().plusSeconds(jwtExpiration))) .signWith(jwtSigningKey); } + + /** + * Creates a derived token with the given settings. Note that the returned string is opaque and + * should not not be interpreted in any way but only used as is. + * + * @param singleUse Whether this token should be deleted after a single use + * @return An opaque string representing the token + */ + @Transactional + public String deriveToken(Boolean singleUse) { + UUID id = UUID.randomUUID(); + TokenEntity tokenEntity = new TokenEntity(); + tokenEntity.setId(id); + if (singleUse != null) { + tokenEntity.setSingleUse(singleUse); + } + + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext.getAuthentication() instanceof FlingToken) { + FlingToken flingToken = (FlingToken) securityContext.getAuthentication(); + tokenEntity.setToken(flingToken.getCredentials()); + } else { + // This should be prevented in FlingWebSecurityConfigurer + throw new IllegalStateException("Cannot derive token from current authentication"); + } + + tokenRepository.save(tokenEntity); + + return id.toString(); + } + + @Transactional + public FlingToken parseDerivedToken(String derivedToken) { + TokenEntity tokenEntity = tokenRepository.getOne(UUID.fromString(derivedToken)); + + FlingToken flingToken = parseJwtAuthentication(tokenEntity.getToken()); + + if (tokenEntity.getSingleUse()) { + tokenRepository.delete(tokenEntity); + } + + return flingToken; + } } diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java index e136e31..1e58c2d 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java @@ -75,7 +75,9 @@ public class AuthorizationService { } FlingEntity flingEntity = flingRepository.findByShareId(shareId); - if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+shareId); } + if (flingEntity == null) { + throw new EntityNotFoundException("No entity for shareId=" + shareId); + } return allowFlingAccess(flingEntity.getId(), token); } diff --git a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java index ecfb474..f2e2fae 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/FlingService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/FlingService.java @@ -90,7 +90,9 @@ public class FlingService { public FlingDto getByShareId(String shareId) { FlingEntity flingEntity = flingRepository.findByShareId(shareId); - if(flingEntity == null) { throw new EntityNotFoundException("No entity for shareId="+shareId); } + if (flingEntity == null) { + throw new EntityNotFoundException("No entity for shareId=" + shareId); + } return flingMapper.map(flingEntity); } diff --git a/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java index 5576e9e..8d5c79e 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/AuthenticationControllerTest.java @@ -84,4 +84,5 @@ public class AuthenticationControllerTest { .andExpect(status().is(200)) .andExpect(content().string("token")); } + } diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java index 658ef44..e829625 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthenticationServiceTest.java @@ -1,24 +1,33 @@ package net.friedl.fling.service; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.security.Key; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import javax.persistence.EntityNotFoundException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; @@ -33,10 +42,13 @@ import io.jsonwebtoken.security.Keys; import net.friedl.fling.model.dto.AdminAuthDto; import net.friedl.fling.model.dto.UserAuthDto; import net.friedl.fling.persistence.entities.FlingEntity; +import net.friedl.fling.persistence.entities.TokenEntity; import net.friedl.fling.persistence.repositories.FlingRepository; -import net.friedl.fling.security.authentication.FlingAdminAuthority; +import net.friedl.fling.persistence.repositories.TokenRepository; +import net.friedl.fling.security.FlingAuthorities; import net.friedl.fling.security.authentication.FlingToken; -import net.friedl.fling.security.authentication.FlingUserAuthority; +import net.friedl.fling.security.authentication.authorities.FlingAdminAuthority; +import net.friedl.fling.security.authentication.authorities.FlingUserAuthority; @ExtendWith(SpringExtension.class) @TestPropertySource("classpath:/application-test.properties") @@ -48,6 +60,9 @@ public class AuthenticationServiceTest { @MockBean private FlingRepository flingRepository; + @MockBean + private TokenRepository tokenRepository; + @MockBean private JwtParser jwtParser; @@ -60,8 +75,11 @@ public class AuthenticationServiceTest { @Bean public AuthenticationService authenticationService(JwtParser jwtParser, - PasswordEncoder passwordEncoder, FlingRepository flingRepository) { - return new AuthenticationService(jwtParser, jwtSigningKey, passwordEncoder, flingRepository); + PasswordEncoder passwordEncoder, FlingRepository flingRepository, + TokenRepository tokenRepository) { + + return new AuthenticationService(jwtParser, jwtSigningKey, passwordEncoder, flingRepository, + tokenRepository); } } @@ -118,23 +136,24 @@ public class AuthenticationServiceTest { @Test public void authenticate_noFlingForShareId_throws() { - UserAuthDto userAuthDto = UserAuthDto.builder() + UserAuthDto userAuthDto = UserAuthDto.builder() .authCode("authCode") .shareId("doesNotExist").build(); when(flingRepository.findByShareId(any(String.class))).thenReturn(null); when(passwordEncoder.encode(any(String.class))).thenReturn("authCodeHash"); - assertThrows(EntityNotFoundException.class, () -> authenticationService.authenticate(userAuthDto)); + assertThrows(EntityNotFoundException.class, + () -> authenticationService.authenticate(userAuthDto)); } @Test - public void parseAuthentication_owner_AdminAuthority() { + public void parseJwtAuthentication_owner_AdminAuthority() { Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), new DefaultClaims(Map.of("sub", "admin")), "signature"); when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); - FlingToken flingToken = authenticationService.parseAuthentication("any"); + FlingToken flingToken = authenticationService.parseJwtAuthentication("any"); assertThat(flingToken.isAuthenticated(), equalTo(true)); // authorized for any fling assertThat(flingToken.authorizedForFling(UUID.randomUUID()), equalTo(true)); @@ -144,12 +163,12 @@ public class AuthenticationServiceTest { } @Test - public void parseAuthentication_user_UserAuthorityForId() { + public void parseJwtAuthentication_user_UserAuthorityForId() { Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), new DefaultClaims(Map.of("sub", "user", "id", new UUID(0, 0).toString())), "signature"); when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); - FlingToken flingToken = authenticationService.parseAuthentication("any"); + FlingToken flingToken = authenticationService.parseJwtAuthentication("any"); assertThat(flingToken.isAuthenticated(), equalTo(true)); // authorized for fling in token assertThat(flingToken.authorizedForFling(new UUID(0, 0)), equalTo(true)); @@ -161,12 +180,110 @@ public class AuthenticationServiceTest { } @Test - public void parseAuthentication_unknownSubject_throws() { + public void parseJwtAuthentication_unknownSubject_throws() { Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), new DefaultClaims(Map.of("sub", "unknownSubject")), "signature"); when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); assertThrows(BadCredentialsException.class, - () -> authenticationService.parseAuthentication("any")); + () -> authenticationService.parseJwtAuthentication("any")); + } + + @Test + public void deriveToken_noAuthenticationInSecurityContext_throws() { + assertThrows(IllegalStateException.class, + () -> authenticationService.deriveToken(false)); + } + + @Test + public void deriveToken_authenticationInSecurityContext_ok() { + FlingToken flingToken = new FlingToken(List.of(new FlingAdminAuthority()), "token"); + SecurityContextHolder.setContext(new SecurityContextImpl(flingToken)); + + String derivedToken = authenticationService.deriveToken(null); + + assertThat(derivedToken, is(not(emptyOrNullString()))); + SecurityContextHolder.clearContext(); + } + + @Test + public void deriveToken_singleUseNotSet_singleUseIsTrue() { + FlingToken flingToken = new FlingToken(List.of(new FlingAdminAuthority()), "token"); + SecurityContextHolder.setContext(new SecurityContextImpl(flingToken)); + + ArgumentCaptor tokenEntityCaptor = ArgumentCaptor.forClass(TokenEntity.class); + + authenticationService.deriveToken(null); + + verify(tokenRepository).save(tokenEntityCaptor.capture()); + assertThat(tokenEntityCaptor.getValue().getSingleUse(), is(true)); + } + + @Test + public void deriveToken_singleUseFalse_singleUseIsFalse() { + FlingToken flingToken = new FlingToken(List.of(new FlingAdminAuthority()), "token"); + SecurityContextHolder.setContext(new SecurityContextImpl(flingToken)); + + ArgumentCaptor tokenEntityCaptor = ArgumentCaptor.forClass(TokenEntity.class); + + authenticationService.deriveToken(false); + + verify(tokenRepository).save(tokenEntityCaptor.capture()); + assertThat(tokenEntityCaptor.getValue().getSingleUse(), is(false)); + } + + @Test + public void parseDerivedToken_singleUse_deletesToken() { + String token = UUID.randomUUID().toString(); + Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), + new DefaultClaims(Map.of("sub", "admin")), "signature"); + TokenEntity tokenEntity = new TokenEntity(); + tokenEntity.setId(UUID.fromString(token)); + tokenEntity.setSingleUse(true); + tokenEntity.setToken("jwtToken"); + + when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); + when(tokenRepository.getOne(any(UUID.class))).thenReturn(tokenEntity); + + authenticationService.parseDerivedToken(token); + + verify(tokenRepository).delete(tokenEntity); + } + + @Test + public void parseDerivedToken_singleUseFalse_doesNotDeleteToken() { + String token = UUID.randomUUID().toString(); + Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), + new DefaultClaims(Map.of("sub", "admin")), "signature"); + TokenEntity tokenEntity = new TokenEntity(); + tokenEntity.setId(UUID.fromString(token)); + tokenEntity.setSingleUse(false); + tokenEntity.setToken("jwtToken"); + + when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); + when(tokenRepository.getOne(any(UUID.class))).thenReturn(tokenEntity); + + authenticationService.parseDerivedToken(token); + + verify(tokenRepository, never()).delete(tokenEntity); + } + + @Test + public void parseDerivedToken_returnsParentAuthentication() { + String token = UUID.randomUUID().toString(); + Jws jwsClaims = new DefaultJws<>(new DefaultJwsHeader(), + new DefaultClaims(Map.of("sub", "admin")), "signature"); + TokenEntity tokenEntity = new TokenEntity(); + tokenEntity.setId(UUID.fromString(token)); + tokenEntity.setSingleUse(true); + tokenEntity.setToken("jwtToken"); + + when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims); + when(tokenRepository.getOne(any(UUID.class))).thenReturn(tokenEntity); + + FlingToken flingToken = authenticationService.parseDerivedToken(token); + + assertEquals(flingToken.getAuthorities().stream().findFirst().get().getAuthority(), + FlingAuthorities.FLING_ADMIN.getAuthority()); } } diff --git a/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java b/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java index 3ad1820..547a2aa 100644 --- a/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java +++ b/service/fling/src/test/java/net/friedl/fling/service/AuthorizationServiceTest.java @@ -19,9 +19,9 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.test.context.junit.jupiter.SpringExtension; import net.friedl.fling.persistence.entities.FlingEntity; import net.friedl.fling.persistence.repositories.FlingRepository; -import net.friedl.fling.security.authentication.FlingAdminAuthority; import net.friedl.fling.security.authentication.FlingToken; -import net.friedl.fling.security.authentication.FlingUserAuthority; +import net.friedl.fling.security.authentication.authorities.FlingAdminAuthority; +import net.friedl.fling.security.authentication.authorities.FlingUserAuthority; @ExtendWith(SpringExtension.class) public class AuthorizationServiceTest { @@ -50,7 +50,7 @@ public class AuthorizationServiceTest { @Test public void allowUpload_flingAdmin_true() { - FlingToken flingToken = new FlingToken(new FlingAdminAuthority(), "jwtToken"); + FlingToken flingToken = new FlingToken(List.of(new FlingAdminAuthority()), "jwtToken"); assertTrue(authorizationService.allowUpload(UUID.randomUUID(), flingToken)); } @@ -59,7 +59,8 @@ public class AuthorizationServiceTest { FlingEntity flingEntity = new FlingEntity(); flingEntity.setAllowUpload(false); - FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + FlingToken flingToken = + new FlingToken(List.of(new FlingUserAuthority(new UUID(0, 0))), "jwtToken"); when(flingRepository.getOne(new UUID(0, 0))).thenReturn(flingEntity); @@ -71,7 +72,8 @@ public class AuthorizationServiceTest { FlingEntity flingEntity = new FlingEntity(); flingEntity.setAllowUpload(true); - FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + FlingToken flingToken = + new FlingToken(List.of(new FlingUserAuthority(new UUID(0, 0))), "jwtToken"); when(flingRepository.getOne(new UUID(1, 1))).thenReturn(flingEntity); @@ -84,7 +86,8 @@ public class AuthorizationServiceTest { FlingEntity flingEntity = new FlingEntity(); flingEntity.setAllowUpload(true); - FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + FlingToken flingToken = + new FlingToken(List.of(new FlingUserAuthority(new UUID(0, 0))), "jwtToken"); when(flingRepository.getOne(new UUID(0, 0))).thenReturn(flingEntity); @@ -101,25 +104,28 @@ public class AuthorizationServiceTest { @Test public void allowFlingAcess_flingAdmin_true() { - FlingToken flingToken = new FlingToken(new FlingAdminAuthority(), "jwtToken"); + FlingToken flingToken = new FlingToken(List.of(new FlingAdminAuthority()), "jwtToken"); assertTrue(authorizationService.allowFlingAccess(UUID.randomUUID(), flingToken)); } @Test public void allowFlingAcess_flingUser_notAuthorizedForId_false() { - FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + FlingToken flingToken = + new FlingToken(List.of(new FlingUserAuthority(new UUID(0, 0))), "jwtToken"); assertFalse(authorizationService.allowFlingAccess(new UUID(1, 1), flingToken)); } @Test public void allowFlingAcess_flingUser_authorizedForId_true() { - FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + FlingToken flingToken = + new FlingToken(List.of(new FlingUserAuthority(new UUID(0, 0))), "jwtToken"); assertTrue(authorizationService.allowFlingAccess(new UUID(0, 0), flingToken)); } @Test public void allowFlingAccessByShareId_noFlingForShareId_throw() { - FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken"); + FlingToken flingToken = + new FlingToken(List.of(new FlingUserAuthority(new UUID(0, 0))), "jwtToken"); when(flingRepository.findByShareId(any(String.class))).thenReturn(null); assertThrows(EntityNotFoundException.class, diff --git a/web/fling/src/components/admin/FlingArtifacts.jsx b/web/fling/src/components/admin/FlingArtifacts.jsx index 52de4cd..8ceb7d3 100644 --- a/web/fling/src/components/admin/FlingArtifacts.jsx +++ b/web/fling/src/components/admin/FlingArtifacts.jsx @@ -1,114 +1,114 @@ import log from 'loglevel'; -import React, {useState, useEffect, useRef} from 'react'; +import React, { useState, useEffect, useRef } from 'react'; +import { useSelector } from 'react-redux'; -import {artifactClient} from '../../util/flingclient'; +import { ArtifactClient, FlingClient } from '../../util/fc'; +import { prettifyTimestamp } from '../../util/fn'; function FlingArtifactControl(props) { - let iframeContainer = useRef(null); + let iframeContainer = useRef(null); + const artifactClient = new ArtifactClient(); - function handleDelete(ev) { - artifactClient.deleteArtifact(props.artifact.id) - .then(() => props.reloadArtifactsFn()); - } + function handleDelete(ev) { + artifactClient.deleteArtifact(props.artifact.id) + .then(() => props.reloadArtifactsFn()); + } - function handleDownload(ev) { - artifactClient.downloadArtifact(props.artifact.id) - .then(url => { - // We need this iframe hack because with a regular href, while - // the browser downloads the file fine, it also reloads the page, hence - // loosing all logs and state - let frame = document.createElement("iframe"); - frame.src = url; - iframeContainer.current.appendChild(frame); - }); - } + function handleDownload(ev) { + artifactClient.downloadArtifactWithHttpInfo(props.artifact.id) + .then(response => { + log.info(response.headers); + var blob = new Blob([response.data], {type: response.type}); + if(window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(blob, response.name); + } + else{ + var elem = window.document.createElement('a'); + elem.href = window.URL.createObjectURL(blob); + elem.download = response.name; + document.body.appendChild(elem); + elem.click(); + document.body.removeChild(elem); + } + }); + } - return( -
    - - - -
    -
    - ); + return ( +
    + + + +
    +
    + ); } function FlingArtifactRow(props) { - let [hovered, setHovered] = useState(false); - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; + let [hovered, setHovered] = useState(false); - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } - - function localizedUploadDate() { - let d = new Date(props.artifact.uploadTime); - return d.toLocaleDateString(); - } - - return( - setHovered(true)} onMouseOut={() => setHovered(false)}> - {props.artifact.name} - {localizedUploadDate()} - {readableBytes(props.artifact.size)} -
  • - { props.errors.map( (err, idx) =>
      {err}
    ) } -
  • -
    - ); - } - - return ( - <> - { props.errors.length > 0 && !props.below ? renderError() : "" } - { props.children } - { props.errors.length > 0 && props.below ? renderError() : "" } - - ); -} diff --git a/web/fling/src/components/user/FlingUser.jsx b/web/fling/src/components/user/FlingUser.jsx index ec13db7..b76a37f 100644 --- a/web/fling/src/components/user/FlingUser.jsx +++ b/web/fling/src/components/user/FlingUser.jsx @@ -1,26 +1,27 @@ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from 'react'; -import {useParams} from 'react-router-dom'; +import { useParams } from 'react-router-dom'; -import {flingClient} from '../../util/flingclient'; +import { FlingClient } from '../../util/fc'; import DirectDownload from './DirectDownload'; import FlingUserList from './FlingUserList'; export default function FlingUser() { - let { shareId } = useParams(); - let [fling, setFling] = useState({}); + let { shareId } = useParams(); + let [fling, setFling] = useState({}); - useEffect(() => { - flingClient.getFlingByShareId(shareId) - .then(f => setFling(f)); - }, [shareId]); + useEffect(() => { + let flingClient = new FlingClient(); + flingClient.getFlingByShareId(shareId) + .then(f => setFling(f)); + }, [shareId]); - return( -
    - {fling.sharing && fling.sharing.directDownload - ? - : } -
    - ); + return ( +
    + {fling.sharing && fling.sharing.directDownload + ? + : } +
    + ); } diff --git a/web/fling/src/components/user/Unlock.jsx b/web/fling/src/components/user/Unlock.jsx index 3dec25c..799be63 100644 --- a/web/fling/src/components/user/Unlock.jsx +++ b/web/fling/src/components/user/Unlock.jsx @@ -1,61 +1,60 @@ import log from 'loglevel'; -import React, {useState, useEffect} from 'react'; -import {useHistory, useLocation} from 'react-router-dom'; +import React, { useState, useEffect } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; -import request, {setAuth} from '../../util/request'; +import { AuthClient, fc } from '../../util/fc'; export default function Unlock() { - const [authCode, setAuthCode] = useState(""); - const history = useHistory(); - const location = useLocation(); - const { from, shareId } = location.state || { from: { pathname: "/admin" }, shareId: "" }; + const [authCode, setAuthCode] = useState(""); + const history = useHistory(); + const location = useLocation(); + const { from, shareId } = location.state || { from: { pathname: "/admin" }, shareId: "" }; - useEffect(() => setAuth(null), []); + useEffect(() => { + let authClient = new AuthClient(); + let userAuth = new fc.UserAuth(location.state.shareId, "") - useEffect(() => { - request.post("/auth/user", {"shareId": location.state.shareId}) - .then(response => { - log.info("Fling is not protected. Logged in successfully."); - setAuth(response.data); - history.replace(location.state.from); - }) - .catch(err => {/* ignored */}); - }, [location, history]); + authClient.authenticateUser({ 'userAuth': userAuth }) + .then(response => { + log.info("Fling is not protected. Logged in successfully."); + sessionStorage.setItem('token', response); + history.replace(location.state.from); + }).catch(error => { + log.info("Fling protected. Could not unlock without code.") + }); + }, [location, history]); - return ( -
    -
    -
    -

    This Fling is locked.

    -
    -
    - -
    - - -
    - -
    -
    + return ( +
    +
    +
    +

    This Fling is locked.

    - ); +
    +
    +
    + setAuthCode(ev.target.value)} /> + +
    +
    +
    +
    +
    + ); - function handleSubmit(ev) { - ev.preventDefault(); + function handleSubmit(ev) { + ev.preventDefault(); + let authClient = new AuthClient(); + let userAuth = new fc.UserAuth(shareId, authCode) - request.post("/auth/user", {"shareId": shareId, "code": authCode}) - .then(response => { - log.info("Logged in successfully"); - setAuth(response.data); - history.replace(from); - }) - .catch(error => { - log.error(error); - }); - }; - - function handleChange(ev) { - let val = ev.target.value; - setAuthCode(val); - }; + authClient.authenticateUser({ 'userAuth': userAuth }) + .then(response => { + log.info("Logged in successfully"); + sessionStorage.setItem('token', response); + history.replace(from); + }).catch(error => { + log.error(error); + }); + }; } From e9ad499850ad99bbcf76d773e927a555892647c9 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 26 Jul 2020 01:33:32 +0200 Subject: [PATCH 28/30] Use new API in DirectDownload, improve redirection 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. --- examples/querysheet.http | 19 ++- .../fling/controller/FlingController.java | 5 +- .../fling/service/AuthenticationService.java | 1 - .../fling/controller/FlingControllerTest.java | 8 +- .../src/components/admin/FlingArtifacts.jsx | 2 +- .../src/components/user/DirectDownload.jsx | 116 +++++++++++------- web/fling/src/components/user/FlingUser.jsx | 21 +++- web/fling/src/components/user/Unlock.jsx | 6 +- 8 files changed, 119 insertions(+), 59 deletions(-) diff --git a/examples/querysheet.http b/examples/querysheet.http index 5e08215..5995cff 100644 --- a/examples/querysheet.http +++ b/examples/querysheet.http @@ -16,13 +16,30 @@ Content-Type: application/json {"adminName": "admin", "adminPassword":"123"} -> 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 http://localhost:8080/api/fling Content-Type: application/json :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 POST http://localhost:8080/api/fling Content-Type: application/json diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index db4dddc..584b248 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -101,11 +101,14 @@ public class FlingController { public ResponseEntity getFlingData(@PathVariable UUID id) throws IOException { FlingDto flingDto = flingService.getById(id); InputStreamResource data = new InputStreamResource(archiveService.getFling(id)); + Long length = data.contentLength(); + + data = new InputStreamResource(archiveService.getFling(id)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + flingDto.getName() + ".zip" + "\"") - .contentLength(200L) // FIXME + .contentLength(length) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(data); } diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java index 2617716..9849d3b 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthenticationService.java @@ -22,7 +22,6 @@ import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import lombok.extern.slf4j.Slf4j; 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.persistence.entities.FlingEntity; import net.friedl.fling.persistence.entities.TokenEntity; diff --git a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java index 22c95c6..22340ba 100644 --- a/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java +++ b/service/fling/src/test/java/net/friedl/fling/controller/FlingControllerTest.java @@ -122,7 +122,7 @@ public class FlingControllerTest { @Test public void replaceFling_ok() throws Exception { FlingDto flingDto = new FlingDto(flingId, "new-name", Instant.EPOCH, "shareId", "new-authCode", - false, true, true, 1, null); + false, true, true, 1, null); mockMvc.perform(put("/api/fling/{id}", flingId) .content(mapper.writeValueAsString(flingDto)) @@ -275,7 +275,11 @@ public class FlingControllerTest { byte[] testZip = new byte[testZipInt.length]; 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)) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) diff --git a/web/fling/src/components/admin/FlingArtifacts.jsx b/web/fling/src/components/admin/FlingArtifacts.jsx index ebe2684..0e71e5b 100644 --- a/web/fling/src/components/admin/FlingArtifacts.jsx +++ b/web/fling/src/components/admin/FlingArtifacts.jsx @@ -26,7 +26,7 @@ function FlingArtifactControl(props) { log.trace(`Generated download url: ${url}`); frame.src = url; iframeContainer.current.appendChild(frame); - }) + }); } return ( diff --git a/web/fling/src/components/user/DirectDownload.jsx b/web/fling/src/components/user/DirectDownload.jsx index 1055018..9981a38 100644 --- a/web/fling/src/components/user/DirectDownload.jsx +++ b/web/fling/src/components/user/DirectDownload.jsx @@ -1,54 +1,82 @@ -import React, {useRef, useState, useEffect} from 'react'; +import log from 'loglevel'; +import React, { useRef, useState, useEffect } from 'react'; -import {flingClient} from '../../util/flingclient'; +import { AuthClient } from '../../util/fc'; export default function FlingUser(props) { - let iframeContainer = useRef(null); - let [packaging, setPackaging] = useState(true); - let [waitingMessage, setWaitingMessage] = useState(""); - let [downloadUrl, setDownloadUrl] = useState(""); + let iframeContainer = useRef(null); + let [packaging, setPackaging] = useState(true); + let [done, setDone] = useState(false); + let [waitingMessage, setWaitingMessage] = useState(""); + let [downloadUrl, setDownloadUrl] = useState(""); - useEffect(handleDownload, []); + useEffect(handleDownload, []); - function handleDownload() { - flingClient.packageFling(props.fling.id) - .then(downloadUrl => { - setPackaging(false); - // We need this iframe hack because with a regular href, while - // the browser downloads the file fine, it also reloads the page, hence - // loosing all logs and state - let frame = document.createElement("iframe"); - frame.src = downloadUrl; - iframeContainer.current.appendChild(frame); - setDownloadUrl(downloadUrl); - }); + function handleDownload() { + let authClient = new AuthClient(); + 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); + // We need this iframe hack because with a regular href, while + // the browser downloads the file fine, it also reloads the page, hence + // loosing all logs and state + let frame = document.createElement("iframe"); + 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); + })); - let randMsg = ["Please stay patient...", - "Your download will be ready soon...", - "Packaging up your files...", - "Almost there..."]; - setInterval(() => setWaitingMessage(randMsg[Math.floor(Math.random() * randMsg.length)]), 10000); - } + let randMsg = ["Please stay patient...", + "Your download will be ready soon...", + "Packaging up your files...", + "Almost there..."]; + setInterval(() => setWaitingMessage(randMsg[Math.floor(Math.random() * randMsg.length)]), 10000); + } - return( -
    -
    -
    -
    - {packaging - ? <>
    - {waitingMessage ? waitingMessage: "Packaging up your files..."} - - : <> -
    Your download is ready!
    -
    - Download doesn't start?
    Click here
    - - } -
    -
    + function invalidateLink(ev) { + setDone(true); + window.location.href = downloadUrl; + } + + function reloadPage(ev) { + window.location.reload(); + } + + return ( +
    +
    +
    +
    + {packaging + ? <>
    + {waitingMessage ? waitingMessage : "Packaging up your files..."} + + : !done + ? <> +
    Your download is ready!
    +
    + Download doesn't start?
    +
    + + : <> +
    Thanks for downloading!
    +
    + Want to download again?
    +
    + + }
    -
    - ); +
    +
    +
    + ); } diff --git a/web/fling/src/components/user/FlingUser.jsx b/web/fling/src/components/user/FlingUser.jsx index b76a37f..96bf1dd 100644 --- a/web/fling/src/components/user/FlingUser.jsx +++ b/web/fling/src/components/user/FlingUser.jsx @@ -10,18 +10,27 @@ import FlingUserList from './FlingUserList'; export default function FlingUser() { let { shareId } = useParams(); let [fling, setFling] = useState({}); + let [loading, setLoading] = useState(true); useEffect(() => { let flingClient = new FlingClient(); flingClient.getFlingByShareId(shareId) - .then(f => setFling(f)); + .then(f => { + setFling(f); + setLoading(false); + }); }, [shareId]); return ( -
    - {fling.sharing && fling.sharing.directDownload - ? - : } -
    + <> + {loading + ?
    + :
    + {fling.shared && fling.directDownload + ? + : } +
    + } + ); } diff --git a/web/fling/src/components/user/Unlock.jsx b/web/fling/src/components/user/Unlock.jsx index 799be63..b30bfe0 100644 --- a/web/fling/src/components/user/Unlock.jsx +++ b/web/fling/src/components/user/Unlock.jsx @@ -12,7 +12,7 @@ export default function Unlock() { useEffect(() => { let authClient = new AuthClient(); - let userAuth = new fc.UserAuth(location.state.shareId, "") + let userAuth = new fc.UserAuth(location.state.shareId, ""); authClient.authenticateUser({ 'userAuth': userAuth }) .then(response => { @@ -20,7 +20,7 @@ export default function Unlock() { sessionStorage.setItem('token', response); history.replace(location.state.from); }).catch(error => { - log.info("Fling protected. Could not unlock without code.") + log.info("Fling protected. Could not unlock without code."); }); }, [location, history]); @@ -46,7 +46,7 @@ export default function Unlock() { function handleSubmit(ev) { ev.preventDefault(); let authClient = new AuthClient(); - let userAuth = new fc.UserAuth(shareId, authCode) + let userAuth = new fc.UserAuth(shareId, authCode); authClient.authenticateUser({ 'userAuth': userAuth }) .then(response => { From 4401659b5c0ea625b8f0265b6c45611080bf0ded Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 26 Jul 2020 02:45:21 +0200 Subject: [PATCH 29/30] Implement new API for user upload and artifacts --- .../fling/service/AuthorizationService.java | 5 +- .../src/components/user/FlingUserList.jsx | 623 ++++++++---------- .../src/components/user/FlingUserUpload.jsx | 25 - 3 files changed, 296 insertions(+), 357 deletions(-) delete mode 100644 web/fling/src/components/user/FlingUserUpload.jsx diff --git a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java index 1e58c2d..8f8fb75 100644 --- a/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/service/AuthorizationService.java @@ -2,6 +2,7 @@ package net.friedl.fling.service; import java.util.UUID; import javax.persistence.EntityNotFoundException; +import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.stereotype.Service; @@ -13,6 +14,7 @@ import net.friedl.fling.security.authentication.FlingToken; @Slf4j @Service +@Transactional public class AuthorizationService { private FlingRepository flingRepository; @@ -32,7 +34,8 @@ public class AuthorizationService { return true; } - if (!flingRepository.getOne(flingId).getAllowUpload()) { + FlingEntity flingEntity = flingRepository.getOne(flingId); + if (flingEntity.getAllowUpload() == null || !flingEntity.getAllowUpload()) { log.debug("Fling[.id={}] does not not allow uploads"); return false; } diff --git a/web/fling/src/components/user/FlingUserList.jsx b/web/fling/src/components/user/FlingUserList.jsx index 8985435..9d213c3 100644 --- a/web/fling/src/components/user/FlingUserList.jsx +++ b/web/fling/src/components/user/FlingUserList.jsx @@ -1,380 +1,341 @@ import log from 'loglevel'; -import React, {useState, useEffect, useRef} from 'react'; +import React, { useState, useEffect, useRef } from 'react'; -import {Switch, Route, useLocation, Link} from "react-router-dom"; +import { Switch, Route, useLocation, Link } from "react-router-dom"; -import {flingClient, artifactClient} from '../../util/flingclient'; +import { FlingClient, AuthClient, ArtifactClient, fc } from '../../util/fc'; +import { prettifyTimestamp, prettifyBytes } from '../../util/fn'; import upload from '../resources/upload.svg'; import drop from '../resources/drop.svg'; function Artifacts(props) { - let [artifacts, setArtifacts] = useState([]); + let [artifacts, setArtifacts] = useState([]); - useEffect(() => { - if(!props.fling) return; + useEffect(() => { + if (!props.fling) return; - artifactClient.getArtifacts(props.fling.id) - .then((artifacts) => setArtifacts(artifacts)); - }, [props.fling]); + let flingClient = new FlingClient(); + flingClient.getArtifacts(props.fling.id) + .then(artifacts => setArtifacts(artifacts)); + }, [props.fling]); - function renderArtifact(artifact) { - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; - - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } - - function localizedDate(t) { - let d = new Date(t); - return d.toLocaleDateString(); - } - - return( -
    -
    -
    -
    - {artifact.name}
    -
    -
    -
    {readableBytes(artifact.size)}
    -
    -
    -
    {localizedDate(artifact.uploadTime)}
    -
    -
    -
    -
    - ); - } + function renderArtifact(artifact) { return ( -
    - {artifacts.map(renderArtifact)} +
    +
    +
    +
    + {artifact.path}
    +
    +
    +
    +
    +
    +
    {prettifyTimestamp(artifact.creationTime)}
    +
    +
    +
    ); + } + + return ( +
    + {artifacts.map(renderArtifact)} +
    + ); } function Upload(props) { - let fileInputRef = useRef(null); - let [files, setFiles] = useState([]); - let [dragging, setDragging] = useState(false); - let [dragCount, setDragCount] = useState(0); + let fileInputRef = useRef(null); + let [files, setFiles] = useState([]); + let [dragging, setDragging] = useState(false); + let [dragCount, setDragCount] = useState(0); - useEffect(() => { - // prevent browser from trying to open the file when drag event - // not recognized properly - window.addEventListener("dragover",function(e){ - e.preventDefault(); - },false); - window.addEventListener("drop",function(e){ - e.preventDefault(); - },false); - }); - - function fileList() { - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; - - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } - - let fileList = []; - files.forEach((file,idx) => { - if(!file.uploaded) { - fileList.push( -
    -
    -
    - deleteFile(idx)}/> -
    {file.name}
    -
    {(new Date(file.lastModified)).toLocaleString()+", "+readableBytes(file.size)}
    -
    -
    -
    - ); - } - }); - - return fileList; - } - - function deleteFile(idx) { - let f = [...files]; - f.splice(idx, 1); - setFiles(f); - } - - function totalSize() { - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; - - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } - - let totalSize = 0; - for(let file of files) { - totalSize += file.size; - } - - return readableBytes(totalSize); - } - - function handleClick(ev) { - fileInputRef.current.click(); - } - - function handleFileInputChange(ev) { - let fileInputFiles = fileInputRef.current.files; - if (!fileInputFiles) { - console.warn("No files selected"); - return; - } - - setFiles([...files, ...fileInputFiles]); - } - - function handleDrop(ev) { - stopEvent(ev); - ev.persist(); - - let evFiles = ev.dataTransfer.files; - - if (!evFiles) { - console.warn("Dropzone triggered without files"); - return; - } - - setFiles([...files, ...fileListToArray(evFiles)]); - setDragging(false); - setDragCount(0); - } - - function fileListToArray(fileList) { - if(fileList === undefined || fileList === null) { - return []; - } - - let arr = []; - for (let i=0; i i.name).join(',')+"]"); - } - - function setFileUploaded(idx) { - let f = [...files]; - f[idx].uploaded = true; - setFiles(f); - } - - function handleUpload() { - files.forEach((file, idx) => { - artifactClient.postArtifact(props.fling.id, file) - .then(response => { - setFileUploaded(idx); - }); - }); - } - - function zoneContent(dragging) { - if(dragging){ - return( - <> - dropzone icon -
    Drop now!
    - - ); - }else { - return( - <> - dropzone icon -
    Click or Drop
    - - ); - } - } - - return( -
    - {logFiles()} -
    -
    -
    - - - {zoneContent(dragging)} + useEffect(() => { + // prevent browser from trying to open the file when drag event + // not recognized properly + window.addEventListener("dragover", function(e) { + e.preventDefault(); + }, false); + window.addEventListener("drop", function(e) { + e.preventDefault(); + }, false); + }); + function fileList() { + let fileList = []; + files.forEach((file, idx) => { + if (!file.uploaded) { + fileList.push( +
    +
    +
    + deleteFile(idx)} /> +
    {file.name}
    +
    {(new Date(file.lastModified)).toLocaleString() + ", " + prettifyBytes(file.size)}
    +
    + ); + } + }); -
    -
    -
    -
    -
    - {fileList()} -
    -
    -
    -
    - Total Size: {totalSize()} - + return fileList; + } + + function deleteFile(idx) { + let f = [...files]; + f.splice(idx, 1); + setFiles(f); + } + + function totalSize() { + let totalSize = 0; + for (let file of files) { + totalSize += file.size; + } + + return prettifyBytes(totalSize); + } + + function handleClick(ev) { + fileInputRef.current.click(); + } + + function handleFileInputChange(ev) { + let fileInputFiles = fileInputRef.current.files; + if (!fileInputFiles) { + console.warn("No files selected"); + return; + } + + setFiles([...files, ...fileInputFiles]); + } + + function handleDrop(ev) { + stopEvent(ev); + ev.persist(); + + let evFiles = ev.dataTransfer.files; + + if (!evFiles) { + console.warn("Dropzone triggered without files"); + return; + } + + setFiles([...files, ...fileListToArray(evFiles)]); + setDragging(false); + setDragCount(0); + } + + function fileListToArray(fileList) { + if (fileList === undefined || fileList === null) { + return []; + } + + let arr = []; + for (let i = 0; i < fileList.length; i++) { arr.push(fileList[i]); } + + return arr; + } + + function handleOnDragEnter(ev) { + stopEvent(ev); + if (dragCount === 0) setDragging(true); + + setDragCount(dragCount + 1); + } + + function handleOnDragLeave(ev) { + stopEvent(ev); + let dc = dragCount; + + dc -= 1; + setDragCount(dc); + + if (dc === 0) setDragging(false); + } + + function stopEvent(ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + function logFiles() { + log.info("Files so far: [" + files.map((i) => i.name).join(',') + "]"); + } + + function setFileUploaded(idx) { + let f = [...files]; + f[idx].uploaded = true; + setFiles(f); + } + + function handleUpload() { + const flingClient = new FlingClient(); + const artifactClient = new ArtifactClient(); + + files.forEach((file, idx) => { + let artifact = new fc.Artifact(file.name) + + flingClient.postArtifact(props.fling.id, { artifact: artifact }) + .then(artifact => { + artifactClient.uploadArtifactData(artifact.id, { body: file }); + setFileUploaded(idx); + }); + }); + } + + function zoneContent(dragging) { + if (dragging) { + return ( + <> + dropzone icon +
    Drop now!
    + + ); + } else { + return ( + <> + dropzone icon +
    Click or Drop
    + + ); + } + } + + return ( +
    + {logFiles()} +
    +
    +
    + + + {zoneContent(dragging)} + +
    +
    + +
    +
    +
    +
    +
    + {fileList()}
    +
    + Total Size: {totalSize()} + +
    - ); +
    +
    + ); } export default function FlingUserList(props) { - let location = useLocation(); + let location = useLocation(); - let iframeContainer = useRef(null); - let [infoText, setInfoText] = useState(""); - let [inProgress, setInProgress] = useState(false); + let iframeContainer = useRef(null); + let [infoText, setInfoText] = useState(""); + let [inProgress, setInProgress] = useState(false); - useEffect((flingId) => { - if(!flingId) return; + useEffect(() => { + if (!props.fling.id) return; - function readableBytes(bytes) { - if(bytes <= 0) return "0 KB"; + let flingClient = new FlingClient(); + flingClient.getArtifacts(props.fling.id) + .then((artifacts) => { + setInfoText(`${prettifyTimestamp(props.fling.creationTime)} - ${artifacts.length} files`); + }); + }, [props.fling.id, props.fling.creationTime]); - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + function handleDownload(ev) { + ev.preventDefault(); - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - } + setInProgress(true); + let authClient = new AuthClient(); + authClient.deriveToken({ singleUse: true }) + .then(token => { + // We need this iframe hack because with a regular href, while + // the browser downloads the file fine, it also reloads the page, hence + // loosing all logs and state + let frame = document.createElement("iframe"); + 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; + setInProgress(false); + iframeContainer.current.appendChild(frame); + }); + } - function localizedDate(t) { - let d = new Date(t); - return d.toLocaleDateString(); - } - - artifactClient.getArtifacts(flingId) - .then((artifacts) => { - let totalSize = 0; - let countArtifacts = 0; - - for(let artifact of artifacts) { - totalSize += artifact.size; - countArtifacts++; - } - - setInfoText(`${localizedDate(props.fling.creationTime)} - ${countArtifacts} files - ${readableBytes(totalSize)}`); - }); - }, [props.fling.id, props.fling.creationTime]); - - function handleDownload(ev) { - ev.preventDefault(); - - setInProgress(true); - - flingClient.packageFling(props.fling.id) - .then(downloadUrl => { - // We need this iframe hack because with a regular href, while - // the browser downloads the file fine, it also reloads the page, hence - // loosing all logs and state - let frame = document.createElement("iframe"); - frame.src = downloadUrl; - iframeContainer.current.appendChild(frame); - setInProgress(false); - }); + function path(tail) { + if (props.fling && props.fling.shareId) { + return `/f/${props.fling.shareId}/${tail}`; } - function path(tail) { - if(props.fling && props.fling.sharing) { - return `/f/${props.fling.sharing.shareUrl}/${tail}`; - } + return ""; + } - return ""; - } + return ( + <> +
    - return( - <> -
    +
    -
    +

    {props.fling.name}

    +
    {infoText}
    -

    {props.fling.name}

    -
    {infoText}
    +
    +
      +
    • + Files +
    • + { props.fling.allowUpload + ?
    • + Upload +
    • + : <> + } -
      -
        -
      • - Files -
      • -
      • - Upload -
      • - -
      • -
        - {inProgress - ? - : - } -
        -
      • -
      - -
      - - - - - -
      -
      - -
      + : + } +
      + +
    +
    + + + + +
    - - ); + +
    + +
    +
    + + ); } diff --git a/web/fling/src/components/user/FlingUserUpload.jsx b/web/fling/src/components/user/FlingUserUpload.jsx deleted file mode 100644 index 6f7c18a..0000000 --- a/web/fling/src/components/user/FlingUserUpload.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import log from 'loglevel'; -import React, {useState, useEffect} from 'react'; - -import {useParams, BrowserRouter} from 'react-router-dom'; - -import {flingClient} from '../../util/flingclient'; - -import DirectDownload from './DirectDownload'; - -export default function FlingUserUpload(props) { - let { shareId } = useParams(); - let [fling, setFling] = useState({}); - - useEffect(() => { - flingClient.getFlingByShareId(shareId) - .then(f => setFling(f)); - }, [shareId]); - - return( -
    - {fling.sharing && fling.sharing.directDownload ? : ""} -
    - ); -} - From d4b7f1db3052f92ba91e47bf883fa2d80bfc3bb6 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 26 Jul 2020 02:48:56 +0200 Subject: [PATCH 30/30] Remove old API utils --- web/fling/src/util/flingclient.js | 124 ------------------------------ web/fling/src/util/request.js | 35 --------- 2 files changed, 159 deletions(-) delete mode 100644 web/fling/src/util/flingclient.js delete mode 100644 web/fling/src/util/request.js diff --git a/web/fling/src/util/flingclient.js b/web/fling/src/util/flingclient.js deleted file mode 100644 index 543ee43..0000000 --- a/web/fling/src/util/flingclient.js +++ /dev/null @@ -1,124 +0,0 @@ -import log from 'loglevel'; - -import request from './request'; - -let flingClient = { - - deleteFling: function(flingId) { - return request.delete(`/fling/${flingId}`) - .then(response => log.info(`Deleted fling ${flingId}`)); - }, - - getFlings: function() { - return request.get('/fling') - .then(response => { - log.info(`Got ${response.data.length} flings`); - return response.data; - }); - }, - - putFling: function(flingId, update) { - return request.put(`/fling/${flingId}`, update) - .then(response => log.info(`Updated fling ${flingId}`)); - }, - - postFling: function(newFling) { - return request.post(`/fling`, newFling) - .then(response => log.info(`Created fling ${response.data}`)); - }, - - getFling: function(flingId) { - return request.get(`/fling?flingId=${flingId}`) - .then(response => { - log.info(`Got fling ${flingId}`); - return response.data; - }); - }, - - getFlingByShareId: function(shareId) { - if(!shareId) return Promise.resolve(null); - - return request.get(`/fling?shareId=${shareId}`) - .then(response => { - log.info(`Got fling ${response.data.id}`); - return response.data; - }) - .catch(err => { - if (err.isAxiosError && err.response.status === 404) { - return null; - } - throw err; - }); - }, - - getShareExists: function(shareId) { - if(!shareId) { - console.info("Empty share id, do not check uniqueness"); - return Promise.resolve(true); - } - - return request.get(`/fling/shareExists/${shareId}`) - .then(response => { - if(response.data){ - log.info(`Share with id ${shareId} already exists`); - } else { - log.info(`Share with id ${shareId} does not exist`); - } - return response.data; - }); - }, - - packageFling: function(flingId) { - return request.get(`/fling/${flingId}/package`) - .then(response => { - log.debug("Got fling download id", response.data); - let url = `${process.env.REACT_APP_API}/fling/${flingId}/download/${response.data}`; - log.debug("Download url", url); - return url; - }); - }, -}; - -let artifactClient = { - - getArtifacts: function(flingId) { - return request.get(`/artifacts?flingId=${flingId}`) - .then(response => { - log.info(`Got ${response.data.length} artifacts`); - return response.data; - }); - }, - - postArtifact: function(flingId, artifact, progressIndicator) { - return request.post(`/artifacts/${flingId}`, artifact) - .then(response => { - log.info(`Uploaded artifact: ${response.data}`); - return request.patch(`/artifacts/${response.data.id}`, - {name: artifact.name, size: artifact.size}); - }) - .then(response => { - log.info(`Updated artifact data: ${response.data}`); - return response.data; - }) - .catch(err => log.error(`Error while uploading artifact: ${err}`)); - }, - - deleteArtifact: function(artifactId) { - return request.delete(`/artifacts/${artifactId}`) - .then(response => { - log.info(`Delete artifact ${artifactId}`); - }); - }, - - downloadArtifact: function(artifactId) { - return request.get(`/artifacts/${artifactId}/downloadid`) - .then(response => { - log.debug("Got download id", response.data); - let url = `${process.env.REACT_APP_API}/artifacts/${artifactId}/${response.data}/download`; - log.debug("Download url", url); - return url; - }); - } -}; - -export {flingClient, artifactClient}; diff --git a/web/fling/src/util/request.js b/web/fling/src/util/request.js deleted file mode 100644 index 22830e8..0000000 --- a/web/fling/src/util/request.js +++ /dev/null @@ -1,35 +0,0 @@ -import axios from 'axios'; -import jwtDecode from 'jwt-decode'; - -function Request() { - let request = axios.create({baseURL: process.env.REACT_APP_API}); - if(sessionStorage.getItem('token')) { - request.defaults.headers = {'Authorization': `Bearer ${sessionStorage.getItem('token')}`}; - } - - return request; -} - -function hasClaim(name, value) { - if(!sessionStorage.getItem('token')) return false; - let tokenPayload = jwtDecode(sessionStorage.getItem('token')); - return tokenPayload[name] === value; -} - -function setAuth(token) { - if(token == null){ // reset the auth - sessionStorage.removeItem('token'); - request.defaults.headers = {}; - return; - } - - sessionStorage.setItem('token', token); - request.defaults.headers = {'Authorization': 'Bearer '+sessionStorage.getItem('token')}; -} - -let request = new Request(); -let isOwner = () => hasClaim("sub", "owner"); -let isUser = (shareId) => hasClaim("sub", "user") && hasClaim("sid", shareId); - -export {isOwner, isUser, setAuth}; -export default request;