diff --git a/.gitignore b/.gitignore index d209a838..2be7de69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # See https://help.github.com/ignore-files/ for more about ignoring files. /api/src/cache/ +/docker-compose.yml +/photos_path # dependencies node_modules/ diff --git a/api/package-lock.json b/api/package-lock.json index ae4e4a33..d40c1136 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -2201,22 +2201,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, - "cookie-parser": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", - "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", - "requires": { - "cookie": "0.3.1", - "cookie-signature": "1.0.6" - }, - "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - } - } - }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/api/package.json b/api/package.json index d9c15598..e2112b32 100644 --- a/api/package.json +++ b/api/package.json @@ -23,7 +23,6 @@ "apollo-server": "^2.8.1", "babel-runtime": "^6.26.0", "body-parser": "^1.19.0", - "cookie-parser": "^1.4.4", "core-js": "^3.1.4", "dotenv": "^8.0.0", "exiftool-vendored": "^8.19.0", diff --git a/api/src/index.js b/api/src/index.js index ecf0f659..c23a0c24 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -3,7 +3,7 @@ import path from 'path' import { ApolloServer } from 'apollo-server-express' import express from 'express' import bodyParser from 'body-parser' -import cookieParser from 'cookie-parser' +import cors from 'cors' import { v1 as neo4j } from 'neo4j-driver' import { makeAugmentedSchema } from 'neo4j-graphql-js' import dotenv from 'dotenv' @@ -18,7 +18,7 @@ dotenv.config() const app = express() app.use(bodyParser.json()) -app.use(cookieParser()) +app.use(cors()) /* * Create an executable GraphQL schema object from GraphQL type definitions @@ -137,7 +137,7 @@ server.applyMiddleware({ app, graphPath }) import loadImageRoutes from './routes/images' -loadImageRoutes({ app, driver }) +loadImageRoutes({ app, driver, scanner }) const httpServer = http.createServer(app) server.installSubscriptionHandlers(httpServer) diff --git a/api/src/routes/images.js b/api/src/routes/images.js index a915f025..8c514a14 100644 --- a/api/src/routes/images.js +++ b/api/src/routes/images.js @@ -3,21 +3,23 @@ import path from 'path' import _ from 'lodash' import config from '../config' import { isRawImage, getImageCachePath } from '../scanner/utils' -import { getUserFromToken } from '../token' +import { getUserFromToken, getTokenFromBearer } from '../token' -function loadImageRoutes({ app, driver }) { +function loadImageRoutes({ app, driver, scanner }) { app.use('/images/:id/:image', async function(req, res) { const { id, image } = req.params let user = null try { - const token = req.cookies.token + const token = getTokenFromBearer(req.headers.authorization) user = await getUserFromToken(token, driver) } catch (err) { return res.status(401).send(err.message) } + console.log('Getting image for user', user.username) + const session = driver.session() const result = await session.run( diff --git a/ui/Dockerfile b/ui/Dockerfile index 5dd35680..11b3be8d 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -17,5 +17,6 @@ RUN npm run build FROM nginx:stable COPY --from=0 /app/dist /usr/share/nginx/html +COPY ./docker-nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 \ No newline at end of file diff --git a/ui/docker-nginx.conf b/ui/docker-nginx.conf new file mode 100644 index 00000000..2ff0b308 --- /dev/null +++ b/ui/docker-nginx.conf @@ -0,0 +1,9 @@ +server { + listen 80 default_server; + + root /usr/share/nginx/html; + + location / { + try_files $uri /index.html; + } +} \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index c19c2650..9067f6a6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -81,12 +81,6 @@ "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true } } }, @@ -788,6 +782,18 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-runtime": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.5.tgz", + "integrity": "sha512-6Xmeidsun5rkwnGfMOp6/z9nSzWpHFNVr2Jx7kwoq4mVatQfQx5S56drBgEHF+XQbKOdIaOiMIINvp/kAwMN+w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "resolve": "^1.8.1", + "semver": "^5.5.1" + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", @@ -2131,11 +2137,6 @@ "safe-buffer": "~5.1.1" } }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", diff --git a/ui/package.json b/ui/package.json index d3a1dad8..bbf34143 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,7 +12,6 @@ "apollo-link-http": "^1.5.15", "apollo-link-ws": "^1.0.18", "babel-plugin-styled-components": "^1.10.6", - "cookie": "^0.4.0", "graphql": "^14.2.1", "graphql-tag": "^2.10.1", "parcel-bundler": "^1.12.3", @@ -38,6 +37,7 @@ }, "devDependencies": { "@babel/core": "^7.5.5", + "@babel/plugin-transform-runtime": "^7.5.5", "husky": "^3.0.2", "isarray": "^2.0.5", "lint-staged": "^9.2.1" @@ -63,7 +63,8 @@ ] ], "plugins": [ - "babel-plugin-styled-components" + "babel-plugin-styled-components", + "@babel/plugin-transform-runtime" ] }, "lint-staged": { diff --git a/ui/src/Pages/AllAlbumsPage/AlbumBox.js b/ui/src/Pages/AllAlbumsPage/AlbumBox.js index 8f6f2ecb..ee4b5fbe 100644 --- a/ui/src/Pages/AllAlbumsPage/AlbumBox.js +++ b/ui/src/Pages/AllAlbumsPage/AlbumBox.js @@ -1,6 +1,8 @@ import React from 'react' import styled from 'styled-components' import { Link } from 'react-router-dom' +import ProtectedImage from '../../components/photoGallery/ProtectedImage' +import { Icon } from 'semantic-ui-react' const AlbumBoxLink = styled(Link)` width: 240px; @@ -10,16 +12,30 @@ const AlbumBoxLink = styled(Link)` color: #222; ` -const AlbumBoxImage = styled.div` - width: 220px; - height: 220px; - margin: auto; - border-radius: 4%; - background-image: url('${props => props.image}'); - background-color: #eee; - background-size: cover; - background-position: center; -` +const AlbumBoxImage = ({ src, ...props }) => { + const Image = styled(ProtectedImage)` + width: 220px; + height: 220px; + margin: auto; + border-radius: 4%; + object-fit: cover; + object-position: center; + ` + + const Placeholder = styled.div` + width: 220px; + height: 220px; + border-radius: 4%; + margin: auto; + background: linear-gradient(#f7f7f7 0%, #eee 100%); + ` + + if (src) { + return + } + + return +} export const AlbumBox = ({ album, ...props }) => { if (!album) { @@ -33,7 +49,7 @@ export const AlbumBox = ({ album, ...props }) => { return ( { - const StyledPhoto = styled(animated.img)` + const StyledPhoto = styled(animated(ProtectedImage))` height: 200px; min-width: 100%; position: relative; diff --git a/ui/src/components/photoGallery/PresentView.js b/ui/src/components/photoGallery/PresentView.js index 1511fe69..bd7893d6 100644 --- a/ui/src/components/photoGallery/PresentView.js +++ b/ui/src/components/photoGallery/PresentView.js @@ -2,6 +2,7 @@ import React from 'react' import styled, { createGlobalStyle } from 'styled-components' import { Query } from 'react-apollo' import gql from 'graphql-tag' +import ProtectedImage from './ProtectedImage' const PresentContainer = styled.div` position: fixed; @@ -34,7 +35,7 @@ const imageQuery = gql` } ` -const PresentImage = styled.img` +const PresentImage = styled(ProtectedImage)` width: 100vw; height: 100vh; object-fit: contain; diff --git a/ui/src/components/photoGallery/ProtectedImage.js b/ui/src/components/photoGallery/ProtectedImage.js new file mode 100644 index 00000000..857d0e5c --- /dev/null +++ b/ui/src/components/photoGallery/ProtectedImage.js @@ -0,0 +1,49 @@ +import React, { useState, useEffect } from 'react' + +/** + * An image that needs a authorization header to load + */ +class ProtectedImage extends React.Component { + constructor(props) { + super(props) + + this.state = { + imgSrc: null, + } + + this.shouldRefresh = true + } + + shouldComponentUpdate(newProps) { + if (newProps.src != this.props.src) this.shouldRefresh = true + + return true + } + + async fetchImage() { + if (this.props.src && this.shouldRefresh) { + this.shouldRefresh = false + + let image = await fetch(this.props.src, { + headers: { + Authorization: `Bearer ${localStorage.getItem('token')}`, + }, + }) + + image = await image.blob() + const url = URL.createObjectURL(image) + + this.setState({ + imgSrc: url, + loadedSrc: this.props.src, + }) + } + } + + render() { + this.fetchImage() + return + } +} + +export default ProtectedImage diff --git a/ui/src/components/sidebar/PhotoSidebar.js b/ui/src/components/sidebar/PhotoSidebar.js index cf1761ca..24ca5282 100644 --- a/ui/src/components/sidebar/PhotoSidebar.js +++ b/ui/src/components/sidebar/PhotoSidebar.js @@ -4,6 +4,7 @@ import { Query } from 'react-apollo' import gql from 'graphql-tag' import { SidebarItem } from './SidebarItem' import { Loader } from 'semantic-ui-react' +import ProtectedImage from '../photoGallery/ProtectedImage' const photoQuery = gql` query sidebarPhoto($id: ID) { @@ -43,7 +44,7 @@ const RightSidebar = styled.div` border-left: 1px solid #eee; ` -const PreviewImage = styled.img` +const PreviewImage = styled(ProtectedImage)` width: 100%; height: 333px; object-fit: contain;