1
Fork 0

Secure photos with header, rather than cookies. Now docker-compose works

This commit is contained in:
viktorstrate 2019-08-09 17:41:25 +02:00
parent de7a6446f8
commit 50bdc86ab0
15 changed files with 118 additions and 59 deletions

2
.gitignore vendored
View File

@ -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/

16
api/package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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)

View File

@ -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(

View File

@ -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

9
ui/docker-nginx.conf Normal file
View File

@ -0,0 +1,9 @@
server {
listen 80 default_server;
root /usr/share/nginx/html;
location / {
try_files $uri /index.html;
}
}

23
ui/package-lock.json generated
View File

@ -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",

View File

@ -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": {

View File

@ -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

View File

@ -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 = '/'
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;