mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-07-28 21:48:46 +00:00
Use cookies for authentication instead of header
This replaces the current implementation where a bearer header holds the auth-token. Now the same token is being sent using a cookie instead. This greatly simplifies fetching resources (images and video), since the header is sent along implicitly with each request.
This commit is contained in:
@@ -27,31 +27,24 @@ func Middleware(db *sql.DB) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bearer := r.Header.Get("Authorization")
|
||||
if bearer == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
if tokenCookie, err := r.Cookie("auth-token"); err == nil {
|
||||
log.Println("Found auth-token cookie")
|
||||
user, err := models.VerifyTokenAndGetUser(db, tokenCookie.Value)
|
||||
if err != nil {
|
||||
log.Printf("Invalid token: %s\n", err)
|
||||
http.Error(w, "invalid authorization token", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// put it in context
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, user)
|
||||
|
||||
// and call the next with our new context
|
||||
r = r.WithContext(ctx)
|
||||
} else {
|
||||
log.Println("Did not find auth-token cookie")
|
||||
}
|
||||
|
||||
token, err := TokenFromBearer(&bearer)
|
||||
if err != nil {
|
||||
log.Printf("Invalid bearer format: %s\n", bearer)
|
||||
http.Error(w, "Invalid authorization header format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := models.VerifyTokenAndGetUser(db, *token)
|
||||
if err != nil {
|
||||
log.Printf("Invalid token: %s\n", err)
|
||||
http.Error(w, "Invalid authorization token", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// put it in context
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, user)
|
||||
|
||||
// and call the next with our new context
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
79
api/routes/authenticate_media.go
Normal file
79
api/routes/authenticate_media.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/viktorstrate/photoview/api/graphql/auth"
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func authenticateMedia(media *models.Media, db *sql.DB, r *http.Request) (success bool, responseMessage string, responseStatus int, errorMessage error) {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
|
||||
if user != nil {
|
||||
row := db.QueryRow("SELECT owner_id FROM album WHERE album.album_id = ?", media.AlbumId)
|
||||
var owner_id int
|
||||
|
||||
if err := row.Scan(&owner_id); err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if owner_id != user.UserID {
|
||||
return false, "invalid credentials", http.StatusForbidden, nil
|
||||
}
|
||||
} else {
|
||||
// Check if photo is authorized with a share token
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
return false, "unauthorized", http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
row := db.QueryRow("SELECT * FROM share_token WHERE value = ?", token)
|
||||
|
||||
shareToken, err := models.NewShareTokenFromRow(row)
|
||||
if err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Validate share token password, if set
|
||||
if shareToken.Password != nil {
|
||||
tokenPassword := r.Header.Get("TokenPassword")
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*shareToken.Password), []byte(tokenPassword)); err != nil {
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return false, "unauthorized", http.StatusForbidden, nil
|
||||
} else {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shareToken.AlbumID != nil && media.AlbumId != *shareToken.AlbumID {
|
||||
// Check child albums
|
||||
row := db.QueryRow(`
|
||||
WITH recursive child_albums AS (
|
||||
SELECT * FROM album WHERE parent_album = ?
|
||||
UNION ALL
|
||||
SELECT child.* FROM album child JOIN child_albums parent ON parent.album_id = child.parent_album
|
||||
)
|
||||
SELECT * FROM child_albums WHERE album_id = ?
|
||||
`, *shareToken.AlbumID, media.AlbumId)
|
||||
|
||||
_, err := models.NewAlbumFromRow(row)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, "unauthorized", http.StatusForbidden, nil
|
||||
}
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
if shareToken.MediaID != nil && media.MediaID != *shareToken.MediaID {
|
||||
return false, "unauthorized", http.StatusForbidden, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, "success", http.StatusAccepted, nil
|
||||
}
|
||||
@@ -11,9 +11,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/viktorstrate/photoview/api/graphql/auth"
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
"github.com/viktorstrate/photoview/api/scanner"
|
||||
)
|
||||
@@ -43,90 +41,13 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
|
||||
w.Write([]byte("internal server error"))
|
||||
}
|
||||
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user != nil {
|
||||
row := db.QueryRow("SELECT owner_id FROM album WHERE album.album_id = ?", media.AlbumId)
|
||||
var owner_id int
|
||||
|
||||
if err := row.Scan(&owner_id); err != nil {
|
||||
log.Printf("WARN: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
return
|
||||
}
|
||||
|
||||
if owner_id != user.UserID {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("invalid credentials"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Check if photo is authorized with a share token
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
row := db.QueryRow("SELECT * FROM share_token WHERE value = ?", token)
|
||||
|
||||
shareToken, err := models.NewShareTokenFromRow(row)
|
||||
if success, response, status, err := authenticateMedia(media, db, r); !success {
|
||||
if err != nil {
|
||||
log.Printf("WARN: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
return
|
||||
log.Printf("WARN: error authenticating photo: %s\n", err)
|
||||
}
|
||||
|
||||
// Validate share token password, if set
|
||||
if shareToken.Password != nil {
|
||||
tokenPassword := r.Header.Get("TokenPassword")
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*shareToken.Password), []byte(tokenPassword)); err != nil {
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("unauthorized"))
|
||||
return
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shareToken.AlbumID != nil && media.AlbumId != *shareToken.AlbumID {
|
||||
// Check child albums
|
||||
row := db.QueryRow(`
|
||||
WITH recursive child_albums AS (
|
||||
SELECT * FROM album WHERE parent_album = ?
|
||||
UNION ALL
|
||||
SELECT child.* FROM album child JOIN child_albums parent ON parent.album_id = child.parent_album
|
||||
)
|
||||
SELECT * FROM child_albums WHERE album_id = ?
|
||||
`, *shareToken.AlbumID, media.AlbumId)
|
||||
|
||||
_, err := models.NewAlbumFromRow(row)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("unauthorized"))
|
||||
return
|
||||
}
|
||||
log.Printf("WARN: %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if shareToken.MediaID != nil && media_id != *shareToken.MediaID {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(response))
|
||||
return
|
||||
}
|
||||
|
||||
var cachedPath string
|
||||
|
||||
@@ -38,6 +38,16 @@ func RegisterVideoRoutes(db *sql.DB, router *mux.Router) {
|
||||
}
|
||||
|
||||
// TODO: Make sure user is authorized to access video
|
||||
if success, response, status, err := authenticateMedia(media, db, r); !success {
|
||||
if err != nil {
|
||||
log.Printf("WARN: error authenticating video: %s\n", err)
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(response))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Video cookies: %d", len(r.Cookies()))
|
||||
|
||||
var cachedPath string
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ func CORSMiddleware(devMode bool) mux.MiddlewareFunc {
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ","))
|
||||
w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ","))
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Expose-Headers", "content-length")
|
||||
|
||||
endpoint := utils.ApiEndpointUrl()
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
||||
import { Route, Redirect } from 'react-router-dom'
|
||||
import gql from 'graphql-tag'
|
||||
import { Query } from 'react-apollo'
|
||||
import { authToken } from './authentication'
|
||||
|
||||
const adminQuery = gql`
|
||||
query adminQuery {
|
||||
@@ -13,13 +14,13 @@ const adminQuery = gql`
|
||||
`
|
||||
|
||||
export const Authorized = ({ children }) => {
|
||||
const token = localStorage.getItem('token')
|
||||
const token = authToken()
|
||||
|
||||
return token ? children : null
|
||||
}
|
||||
|
||||
const AuthorizedRoute = ({ component: Component, admin = false, ...props }) => {
|
||||
const token = localStorage.getItem('token')
|
||||
const token = authToken()
|
||||
|
||||
let unauthorizedRedirect = null
|
||||
if (!token) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button, Form, Message, Header } from 'semantic-ui-react'
|
||||
import { Container } from './LoginPage'
|
||||
|
||||
import { checkInitialSetupQuery, login } from './loginUtilFunctions'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
const initialSetupMutation = gql`
|
||||
mutation InitialSetup(
|
||||
@@ -53,7 +54,7 @@ class InitialSetupPage extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (localStorage.getItem('token')) {
|
||||
if (authToken()) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Header,
|
||||
} from 'semantic-ui-react'
|
||||
import { checkInitialSetupQuery, login } from './loginUtilFunctions'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
import logoPath from '../../assets/photoview-logo.svg'
|
||||
|
||||
@@ -69,7 +70,7 @@ class LoginPage extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (localStorage.getItem('token')) {
|
||||
if (authToken()) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { saveTokenCookie } from '../../authentication'
|
||||
|
||||
export const checkInitialSetupQuery = gql`
|
||||
query CheckInitialSetup {
|
||||
@@ -9,6 +10,6 @@ export const checkInitialSetupQuery = gql`
|
||||
`
|
||||
|
||||
export function login(token) {
|
||||
localStorage.setItem('token', token)
|
||||
saveTokenCookie(token)
|
||||
window.location = '/'
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ const MediaView = ({ media }) => {
|
||||
|
||||
if (media.type == 'video') {
|
||||
return (
|
||||
<DisplayVideo controls key={media.id}>
|
||||
<DisplayVideo
|
||||
controls
|
||||
key={media.id}
|
||||
crossorigin="use-credentials"
|
||||
poster={media.thumbnail.url}
|
||||
>
|
||||
<source src={media.videoWeb.url} type="video/mp4" />
|
||||
</DisplayVideo>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Route, Switch, Redirect } from 'react-router-dom'
|
||||
|
||||
import { Loader } from 'semantic-ui-react'
|
||||
import Layout from './Layout'
|
||||
import { clearTokenCookie } from './authentication'
|
||||
|
||||
const AlbumsPage = React.lazy(() => import('./Pages/AllAlbumsPage/AlbumsPage'))
|
||||
const AlbumPage = React.lazy(() => import('./Pages/AlbumPage/AlbumPage'))
|
||||
@@ -33,7 +34,7 @@ class Routes extends React.Component {
|
||||
<Route path="/login" component={LoginPage} />
|
||||
<Route path="/logout">
|
||||
{() => {
|
||||
localStorage.removeItem('token')
|
||||
clearTokenCookie()
|
||||
location.href = '/'
|
||||
}}
|
||||
</Route>
|
||||
|
||||
@@ -8,14 +8,15 @@ import { ApolloLink, split } from 'apollo-link'
|
||||
import { getMainDefinition } from 'apollo-utilities'
|
||||
import { MessageState } from './components/messages/Messages'
|
||||
import urlJoin from 'url-join'
|
||||
import { clearTokenCookie } from './authentication'
|
||||
|
||||
const GRAPHQL_ENDPOINT = process.env.API_ENDPOINT
|
||||
export const GRAPHQL_ENDPOINT = process.env.API_ENDPOINT
|
||||
? urlJoin(process.env.API_ENDPOINT, '/graphql')
|
||||
: urlJoin(location.origin, '/api/graphql')
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: GRAPHQL_ENDPOINT,
|
||||
credentials: 'same-origin',
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
console.log('GRAPHQL ENDPOINT', GRAPHQL_ENDPOINT)
|
||||
@@ -27,13 +28,7 @@ websocketUri.protocol = apiProtocol === 'https:' ? 'wss:' : 'ws:'
|
||||
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: websocketUri,
|
||||
credentials: 'same-origin',
|
||||
options: {
|
||||
reconnect: true,
|
||||
connectionParams: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
const link = split(
|
||||
@@ -76,7 +71,7 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
|
||||
|
||||
if (networkError) {
|
||||
console.log(`[Network error]: ${JSON.stringify(networkError)}`)
|
||||
localStorage.removeItem('token')
|
||||
clearTokenCookie()
|
||||
|
||||
const errors = networkError.result.errors
|
||||
|
||||
@@ -106,20 +101,9 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
|
||||
}
|
||||
})
|
||||
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
// get the authentication token from local storage if it exists
|
||||
const token = localStorage.getItem('token')
|
||||
// return the headers to the context so httpLink can read them
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : '',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const client = new ApolloClient({
|
||||
link: ApolloLink.from([linkError, authLink.concat(link)]),
|
||||
// link: ApolloLink.from([linkError, authLink.concat(link)]),
|
||||
link: ApolloLink.from([linkError, link]),
|
||||
cache: new InMemoryCache(),
|
||||
})
|
||||
|
||||
|
||||
14
ui/src/authentication.js
Normal file
14
ui/src/authentication.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export function saveTokenCookie(token) {
|
||||
const maxAge = 14 * 24 * 60 * 60
|
||||
|
||||
document.cookie = `auth-token=${token} ;max-age=${maxAge} ;path=/ ;sameSite=Lax`
|
||||
}
|
||||
|
||||
export function clearTokenCookie() {
|
||||
document.cookie = 'auth-token= ;max-age=0'
|
||||
}
|
||||
|
||||
export function authToken() {
|
||||
const match = document.cookie.match(/auth-token=([\d\w]+)/)
|
||||
return match && match[1]
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { SidebarContext } from './sidebar/Sidebar'
|
||||
import AlbumSidebar from './sidebar/AlbumSidebar'
|
||||
import gql from 'graphql-tag'
|
||||
import { useLazyQuery } from '@apollo/react-hooks'
|
||||
import { authToken } from '../authentication'
|
||||
|
||||
const Header = styled.h1`
|
||||
margin: 24px 0 8px 0 !important;
|
||||
@@ -55,7 +56,7 @@ const AlbumTitle = ({ album, disableLink = false }) => {
|
||||
useEffect(() => {
|
||||
if (!album) return
|
||||
|
||||
if (localStorage.getItem('token') && disableLink == true) {
|
||||
if (authToken() && disableLink == true) {
|
||||
fetchPath({
|
||||
variables: {
|
||||
id: album.id,
|
||||
@@ -93,7 +94,7 @@ const AlbumTitle = ({ album, disableLink = false }) => {
|
||||
<Header>
|
||||
<Breadcrumb>{breadcrumbSections}</Breadcrumb>
|
||||
{title}
|
||||
{localStorage.getItem('token') && (
|
||||
{authToken() && (
|
||||
<SettingsIcon
|
||||
onClick={() => {
|
||||
updateSidebar(<AlbumSidebar albumId={album.id} />)
|
||||
|
||||
@@ -3,6 +3,7 @@ import styled from 'styled-components'
|
||||
import SearchBar from './Searchbar'
|
||||
|
||||
import logoPath from '../../assets/photoview-logo.svg'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
const Container = styled.div`
|
||||
height: 60px;
|
||||
@@ -40,7 +41,7 @@ const Header = () => (
|
||||
<Logo src={logoPath} alt="logo" />
|
||||
<LogoText>Photoview</LogoText>
|
||||
</Title>
|
||||
{localStorage.getItem('token') ? <SearchBar /> : null}
|
||||
{authToken() ? <SearchBar /> : null}
|
||||
</Container>
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import { useTransition, animated } from 'react-spring'
|
||||
import styled from 'styled-components'
|
||||
import { Message } from 'semantic-ui-react'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
import MessageProgress from './MessageProgress'
|
||||
import SubscriptionsHook from './SubscriptionsHook'
|
||||
@@ -124,7 +125,7 @@ const Messages = () => {
|
||||
</animated.div>
|
||||
)
|
||||
})}
|
||||
{localStorage.getItem('token') && (
|
||||
{authToken() && (
|
||||
<SubscriptionsHook messages={messages} setMessages={setMessages} />
|
||||
)}
|
||||
</Container>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useSubscription } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
const notificationSubscription = gql`
|
||||
subscription notificationSubscription {
|
||||
@@ -21,7 +22,7 @@ const notificationSubscription = gql`
|
||||
let messageTimeoutHandles = new Map()
|
||||
|
||||
const SubscriptionsHook = ({ messages, setMessages }) => {
|
||||
if (!localStorage.getItem('token')) {
|
||||
if (!authToken()) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
let imageCache = {}
|
||||
// let imageCache = {}
|
||||
|
||||
export async function fetchProtectedImage(
|
||||
src,
|
||||
{ signal, headers: customHeaders } = { signal: null, headers: null }
|
||||
) {
|
||||
if (src) {
|
||||
if (imageCache[src]) {
|
||||
return imageCache[src]
|
||||
}
|
||||
// export async function fetchProtectedImage(
|
||||
// src,
|
||||
// { signal, headers: customHeaders } = { signal: null, headers: null }
|
||||
// ) {
|
||||
// if (src) {
|
||||
// if (imageCache[src]) {
|
||||
// return imageCache[src]
|
||||
// }
|
||||
|
||||
let headers = {
|
||||
...customHeaders,
|
||||
}
|
||||
if (localStorage.getItem('token')) {
|
||||
headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
// let headers = {
|
||||
// ...customHeaders,
|
||||
// }
|
||||
// if (localStorage.getItem('token')) {
|
||||
// headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`
|
||||
// }
|
||||
|
||||
let image = await fetch(src, {
|
||||
headers,
|
||||
signal,
|
||||
})
|
||||
// let image = await fetch(src, {
|
||||
// headers,
|
||||
// signal,
|
||||
// })
|
||||
|
||||
image = await image.blob()
|
||||
const url = URL.createObjectURL(image)
|
||||
// image = await image.blob()
|
||||
// const url = URL.createObjectURL(image)
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
imageCache[src] = url
|
||||
// // eslint-disable-next-line require-atomic-updates
|
||||
// imageCache[src] = url
|
||||
|
||||
return url
|
||||
}
|
||||
}
|
||||
// return url
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* An image that needs a authorization header to load
|
||||
*/
|
||||
const ProtectedImage = ({ src, ...props }) => {
|
||||
const [imgSrc, setImgSrc] = useState(null)
|
||||
// const [imgSrc, setImgSrc] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (imageCache[src]) return
|
||||
// useEffect(() => {
|
||||
// if (imageCache[src]) return
|
||||
|
||||
const fetchController = new AbortController()
|
||||
let canceled = false
|
||||
// const fetchController = new AbortController()
|
||||
// let canceled = false
|
||||
|
||||
setImgSrc('')
|
||||
// setImgSrc('')
|
||||
|
||||
const imgUrl = new URL(src)
|
||||
const fetchHeaders = {}
|
||||
// const imgUrl = new URL(src)
|
||||
// const fetchHeaders = {}
|
||||
|
||||
if (localStorage.getItem('token') == null) {
|
||||
// Get share token if not authorized
|
||||
// if (localStorage.getItem('token') == null) {
|
||||
// // Get share token if not authorized
|
||||
|
||||
const tokenRegex = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||
if (tokenRegex) {
|
||||
const token = tokenRegex[1]
|
||||
imgUrl.searchParams.set('token', token)
|
||||
// const tokenRegex = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||
// if (tokenRegex) {
|
||||
// const token = tokenRegex[1]
|
||||
// imgUrl.searchParams.set('token', token)
|
||||
|
||||
const tokenPassword = sessionStorage.getItem(`share-token-pw-${token}`)
|
||||
if (tokenPassword) {
|
||||
fetchHeaders['TokenPassword'] = tokenPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
// const tokenPassword = sessionStorage.getItem(`share-token-pw-${token}`)
|
||||
// if (tokenPassword) {
|
||||
// fetchHeaders['TokenPassword'] = tokenPassword
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fetchProtectedImage(imgUrl.href, {
|
||||
signal: fetchController.signal,
|
||||
headers: fetchHeaders,
|
||||
})
|
||||
.then(newSrc => {
|
||||
if (!canceled) {
|
||||
setImgSrc(newSrc)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Fetch image error', error.message)
|
||||
})
|
||||
// fetchProtectedImage(imgUrl.href, {
|
||||
// signal: fetchController.signal,
|
||||
// headers: fetchHeaders,
|
||||
// })
|
||||
// .then(newSrc => {
|
||||
// if (!canceled) {
|
||||
// setImgSrc(newSrc)
|
||||
// }
|
||||
// })
|
||||
// .catch(error => {
|
||||
// console.log('Fetch image error', error.message)
|
||||
// })
|
||||
|
||||
return function cleanup() {
|
||||
canceled = true
|
||||
fetchController.abort()
|
||||
}
|
||||
}, [src])
|
||||
// return function cleanup() {
|
||||
// canceled = true
|
||||
// fetchController.abort()
|
||||
// }
|
||||
// }, [src])
|
||||
|
||||
return <img {...props} src={imageCache[src] || imgSrc} />
|
||||
return <img {...props} src={src} crossOrigin="use-credentials" />
|
||||
}
|
||||
|
||||
ProtectedImage.propTypes = {
|
||||
|
||||
@@ -41,7 +41,12 @@ const PresentMedia = ({ media, imageLoaded, ...otherProps }) => {
|
||||
if (media.type == 'video') {
|
||||
return (
|
||||
<div {...otherProps}>
|
||||
<StyledVideo controls key={media.id}>
|
||||
<StyledVideo
|
||||
controls
|
||||
key={media.id}
|
||||
crossorigin="use-credentials"
|
||||
poster={media.thumbnail.url}
|
||||
>
|
||||
<source src={media.videoWeb.url} type="video/mp4" />
|
||||
</StyledVideo>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import SidebarItem from './SidebarItem'
|
||||
import ProtectedImage from '../photoGallery/ProtectedImage'
|
||||
import SidebarShare from './Sharing'
|
||||
import SidebarDownload from './SidebarDownload'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
const mediaQuery = gql`
|
||||
query sidebarPhoto($id: Int!) {
|
||||
@@ -87,7 +88,12 @@ const PreviewMedia = ({ media, previewImage }) => {
|
||||
|
||||
if (media.type == 'video') {
|
||||
return (
|
||||
<PreviewVideo controls key={media.id}>
|
||||
<PreviewVideo
|
||||
controls
|
||||
key={media.id}
|
||||
crossorigin="use-credentials"
|
||||
poster={media.thumbnail.url}
|
||||
>
|
||||
<source src={media.videoWeb.url} type="video/mp4" />
|
||||
</PreviewVideo>
|
||||
)
|
||||
@@ -225,7 +231,7 @@ const MediaSidebar = ({ media, hidePreview }) => {
|
||||
const [loadMedia, { loading, error, data }] = useLazyQuery(mediaQuery)
|
||||
|
||||
useEffect(() => {
|
||||
if (media != null && localStorage.getItem('token')) {
|
||||
if (media != null && authToken()) {
|
||||
loadMedia({
|
||||
variables: {
|
||||
id: media.id,
|
||||
@@ -236,7 +242,7 @@ const MediaSidebar = ({ media, hidePreview }) => {
|
||||
|
||||
if (!media) return null
|
||||
|
||||
if (!localStorage.getItem('token')) {
|
||||
if (!authToken()) {
|
||||
return <SidebarContent media={media} hidePreview={hidePreview} />
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Icon,
|
||||
} from 'semantic-ui-react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
const sharePhotoQuery = gql`
|
||||
query sidbarGetPhotoShares($id: Int!) {
|
||||
@@ -218,7 +219,7 @@ ShareItemMoreDropdown.propTypes = {
|
||||
|
||||
const SidebarShare = ({ photo, album }) => {
|
||||
if ((!photo || !photo.id) && (!album || !album.id)) return null
|
||||
if (!localStorage.getItem('token')) return null
|
||||
if (!authToken()) return null
|
||||
|
||||
const isPhoto = !!photo
|
||||
const id = isPhoto ? photo.id : album.id
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MessageState } from '../messages/Messages'
|
||||
import { useLazyQuery } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import download from 'downloadjs'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
const downloadQuery = gql`
|
||||
query sidebarDownloadQuery($mediaId: Int!) {
|
||||
@@ -30,22 +31,22 @@ function formatBytes(bytes) {
|
||||
|
||||
const downloadPhoto = async url => {
|
||||
const imgUrl = new URL(url)
|
||||
let headers = {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
}
|
||||
// let headers = {
|
||||
// Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
// }
|
||||
|
||||
if (localStorage.getItem('token') == null) {
|
||||
if (authToken() == null) {
|
||||
// Get share token if not authorized
|
||||
const token = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||
if (token) {
|
||||
imgUrl.searchParams.set('token', token[1])
|
||||
}
|
||||
|
||||
headers = {}
|
||||
// headers = {}
|
||||
}
|
||||
|
||||
const response = await fetch(imgUrl.href, {
|
||||
headers,
|
||||
// headers,
|
||||
})
|
||||
|
||||
const totalBytes = Number(response.headers.get('content-length'))
|
||||
|
||||
Reference in New Issue
Block a user