Secure photos with header, rather than cookies. Now docker-compose works
This commit is contained in:
parent
de7a6446f8
commit
50bdc86ab0
|
@ -1,6 +1,8 @@
|
||||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
/api/src/cache/
|
/api/src/cache/
|
||||||
|
/docker-compose.yml
|
||||||
|
/photos_path
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
|
@ -2201,22 +2201,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
"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": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
"apollo-server": "^2.8.1",
|
"apollo-server": "^2.8.1",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"cookie-parser": "^1.4.4",
|
|
||||||
"core-js": "^3.1.4",
|
"core-js": "^3.1.4",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"exiftool-vendored": "^8.19.0",
|
"exiftool-vendored": "^8.19.0",
|
||||||
|
|
|
@ -3,7 +3,7 @@ import path from 'path'
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import bodyParser from 'body-parser'
|
import bodyParser from 'body-parser'
|
||||||
import cookieParser from 'cookie-parser'
|
import cors from 'cors'
|
||||||
import { v1 as neo4j } from 'neo4j-driver'
|
import { v1 as neo4j } from 'neo4j-driver'
|
||||||
import { makeAugmentedSchema } from 'neo4j-graphql-js'
|
import { makeAugmentedSchema } from 'neo4j-graphql-js'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
@ -18,7 +18,7 @@ dotenv.config()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
app.use(cookieParser())
|
app.use(cors())
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create an executable GraphQL schema object from GraphQL type definitions
|
* Create an executable GraphQL schema object from GraphQL type definitions
|
||||||
|
@ -137,7 +137,7 @@ server.applyMiddleware({ app, graphPath })
|
||||||
|
|
||||||
import loadImageRoutes from './routes/images'
|
import loadImageRoutes from './routes/images'
|
||||||
|
|
||||||
loadImageRoutes({ app, driver })
|
loadImageRoutes({ app, driver, scanner })
|
||||||
|
|
||||||
const httpServer = http.createServer(app)
|
const httpServer = http.createServer(app)
|
||||||
server.installSubscriptionHandlers(httpServer)
|
server.installSubscriptionHandlers(httpServer)
|
||||||
|
|
|
@ -3,21 +3,23 @@ import path from 'path'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
import { isRawImage, getImageCachePath } from '../scanner/utils'
|
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) {
|
app.use('/images/:id/:image', async function(req, res) {
|
||||||
const { id, image } = req.params
|
const { id, image } = req.params
|
||||||
|
|
||||||
let user = null
|
let user = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = req.cookies.token
|
const token = getTokenFromBearer(req.headers.authorization)
|
||||||
user = await getUserFromToken(token, driver)
|
user = await getUserFromToken(token, driver)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.status(401).send(err.message)
|
return res.status(401).send(err.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Getting image for user', user.username)
|
||||||
|
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
|
|
||||||
const result = await session.run(
|
const result = await session.run(
|
||||||
|
|
|
@ -17,5 +17,6 @@ RUN npm run build
|
||||||
FROM nginx:stable
|
FROM nginx:stable
|
||||||
|
|
||||||
COPY --from=0 /app/dist /usr/share/nginx/html
|
COPY --from=0 /app/dist /usr/share/nginx/html
|
||||||
|
COPY ./docker-nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
|
@ -0,0 +1,9 @@
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,12 +81,6 @@
|
||||||
"lodash": "^4.17.13",
|
"lodash": "^4.17.13",
|
||||||
"to-fast-properties": "^2.0.0"
|
"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/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": {
|
"@babel/plugin-transform-shorthand-properties": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
|
"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"
|
"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": {
|
"copy-descriptor": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
"apollo-link-http": "^1.5.15",
|
"apollo-link-http": "^1.5.15",
|
||||||
"apollo-link-ws": "^1.0.18",
|
"apollo-link-ws": "^1.0.18",
|
||||||
"babel-plugin-styled-components": "^1.10.6",
|
"babel-plugin-styled-components": "^1.10.6",
|
||||||
"cookie": "^0.4.0",
|
|
||||||
"graphql": "^14.2.1",
|
"graphql": "^14.2.1",
|
||||||
"graphql-tag": "^2.10.1",
|
"graphql-tag": "^2.10.1",
|
||||||
"parcel-bundler": "^1.12.3",
|
"parcel-bundler": "^1.12.3",
|
||||||
|
@ -38,6 +37,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@babel/core": "^7.5.5",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.5.5",
|
||||||
"husky": "^3.0.2",
|
"husky": "^3.0.2",
|
||||||
"isarray": "^2.0.5",
|
"isarray": "^2.0.5",
|
||||||
"lint-staged": "^9.2.1"
|
"lint-staged": "^9.2.1"
|
||||||
|
@ -63,7 +63,8 @@
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"babel-plugin-styled-components"
|
"babel-plugin-styled-components",
|
||||||
|
"@babel/plugin-transform-runtime"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import ProtectedImage from '../../components/photoGallery/ProtectedImage'
|
||||||
|
import { Icon } from 'semantic-ui-react'
|
||||||
|
|
||||||
const AlbumBoxLink = styled(Link)`
|
const AlbumBoxLink = styled(Link)`
|
||||||
width: 240px;
|
width: 240px;
|
||||||
|
@ -10,16 +12,30 @@ const AlbumBoxLink = styled(Link)`
|
||||||
color: #222;
|
color: #222;
|
||||||
`
|
`
|
||||||
|
|
||||||
const AlbumBoxImage = styled.div`
|
const AlbumBoxImage = ({ src, ...props }) => {
|
||||||
width: 220px;
|
const Image = styled(ProtectedImage)`
|
||||||
height: 220px;
|
width: 220px;
|
||||||
margin: auto;
|
height: 220px;
|
||||||
border-radius: 4%;
|
margin: auto;
|
||||||
background-image: url('${props => props.image}');
|
border-radius: 4%;
|
||||||
background-color: #eee;
|
object-fit: cover;
|
||||||
background-size: cover;
|
object-position: center;
|
||||||
background-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 <Image {...props} src={src} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Placeholder />
|
||||||
|
}
|
||||||
|
|
||||||
export const AlbumBox = ({ album, ...props }) => {
|
export const AlbumBox = ({ album, ...props }) => {
|
||||||
if (!album) {
|
if (!album) {
|
||||||
|
@ -33,7 +49,7 @@ export const AlbumBox = ({ album, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<AlbumBoxLink {...props} to={`/album/${album.id}`}>
|
<AlbumBoxLink {...props} to={`/album/${album.id}`}>
|
||||||
<AlbumBoxImage
|
<AlbumBoxImage
|
||||||
image={
|
src={
|
||||||
album.photos[0] &&
|
album.photos[0] &&
|
||||||
album.photos[0].thumbnail &&
|
album.photos[0].thumbnail &&
|
||||||
album.photos[0].thumbnail.url
|
album.photos[0].thumbnail.url
|
||||||
|
|
|
@ -8,15 +8,7 @@ export const checkInitialSetupQuery = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export function setCookie(cname, cvalue, exdays) {
|
|
||||||
var d = new Date()
|
|
||||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
|
|
||||||
var expires = 'expires=' + d.toUTCString()
|
|
||||||
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function login(token) {
|
export function login(token) {
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
setCookie('token', token, 360)
|
|
||||||
window.location = '/'
|
window.location = '/'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { useSpring, animated } from 'react-spring'
|
import { useSpring, animated } from 'react-spring'
|
||||||
import LazyLoad from 'react-lazyload'
|
import LazyLoad from 'react-lazyload'
|
||||||
import { Icon } from 'semantic-ui-react'
|
import { Icon } from 'semantic-ui-react'
|
||||||
|
import ProtectedImage from './ProtectedImage'
|
||||||
|
|
||||||
const PhotoContainer = styled.div`
|
const PhotoContainer = styled.div`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -13,7 +14,7 @@ const PhotoContainer = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const PhotoImg = photoProps => {
|
const PhotoImg = photoProps => {
|
||||||
const StyledPhoto = styled(animated.img)`
|
const StyledPhoto = styled(animated(ProtectedImage))`
|
||||||
height: 200px;
|
height: 200px;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||||
import styled, { createGlobalStyle } from 'styled-components'
|
import styled, { createGlobalStyle } from 'styled-components'
|
||||||
import { Query } from 'react-apollo'
|
import { Query } from 'react-apollo'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
import ProtectedImage from './ProtectedImage'
|
||||||
|
|
||||||
const PresentContainer = styled.div`
|
const PresentContainer = styled.div`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -34,7 +35,7 @@ const imageQuery = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const PresentImage = styled.img`
|
const PresentImage = styled(ProtectedImage)`
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
|
@ -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 <img {...this.props} src={this.state.imgSrc} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProtectedImage
|
|
@ -4,6 +4,7 @@ import { Query } from 'react-apollo'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { SidebarItem } from './SidebarItem'
|
import { SidebarItem } from './SidebarItem'
|
||||||
import { Loader } from 'semantic-ui-react'
|
import { Loader } from 'semantic-ui-react'
|
||||||
|
import ProtectedImage from '../photoGallery/ProtectedImage'
|
||||||
|
|
||||||
const photoQuery = gql`
|
const photoQuery = gql`
|
||||||
query sidebarPhoto($id: ID) {
|
query sidebarPhoto($id: ID) {
|
||||||
|
@ -43,7 +44,7 @@ const RightSidebar = styled.div`
|
||||||
border-left: 1px solid #eee;
|
border-left: 1px solid #eee;
|
||||||
`
|
`
|
||||||
|
|
||||||
const PreviewImage = styled.img`
|
const PreviewImage = styled(ProtectedImage)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 333px;
|
height: 333px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
Loading…
Reference in New Issue