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.
|
||||
|
||||
/api/src/cache/
|
||||
/docker-compose.yml
|
||||
/photos_path
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
|
@ -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",
|
||||
"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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 <Image {...props} src={src} />
|
||||
}
|
||||
|
||||
return <Placeholder />
|
||||
}
|
||||
|
||||
export const AlbumBox = ({ album, ...props }) => {
|
||||
if (!album) {
|
||||
|
@ -33,7 +49,7 @@ export const AlbumBox = ({ album, ...props }) => {
|
|||
return (
|
||||
<AlbumBoxLink {...props} to={`/album/${album.id}`}>
|
||||
<AlbumBoxImage
|
||||
image={
|
||||
src={
|
||||
album.photos[0] &&
|
||||
album.photos[0].thumbnail &&
|
||||
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) {
|
||||
localStorage.setItem('token', token)
|
||||
setCookie('token', token, 360)
|
||||
window.location = '/'
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { useSpring, animated } from 'react-spring'
|
||||
import LazyLoad from 'react-lazyload'
|
||||
import { Icon } from 'semantic-ui-react'
|
||||
import ProtectedImage from './ProtectedImage'
|
||||
|
||||
const PhotoContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
|
@ -13,7 +14,7 @@ const PhotoContainer = styled.div`
|
|||
`
|
||||
|
||||
const PhotoImg = photoProps => {
|
||||
const StyledPhoto = styled(animated.img)`
|
||||
const StyledPhoto = styled(animated(ProtectedImage))`
|
||||
height: 200px;
|
||||
min-width: 100%;
|
||||
position: relative;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 { 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;
|
||||
|
|
Loading…
Reference in New Issue