Prepare back-end for token password

This commit is contained in:
viktorstrate
2020-06-14 17:58:50 +02:00
parent bbb6e2eca2
commit f6f6eb8bfa
3 changed files with 54 additions and 11 deletions

View File

@@ -60,10 +60,15 @@ func (r *shareTokenResolver) HasPassword(ctx context.Context, obj *models.ShareT
return hasPassword, nil
}
func (r *queryResolver) ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error) {
func (r *queryResolver) ShareToken(ctx context.Context, tokenValue string, password *string) (*models.ShareToken, error) {
row := r.Database.QueryRow("SELECT * FROM share_token WHERE value = ? AND (password = ? OR password IS NULL)", token, password)
result, err := models.NewShareTokenFromRow(row)
hashed_password, err := hashSharePassword(password)
if err != nil {
return nil, errors.Wrap(err, "Failed to hash password")
}
row := r.Database.QueryRow("SELECT * FROM share_token WHERE value = ?", tokenValue)
token, err := models.NewShareTokenFromRow(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.New("share not found")
@@ -72,7 +77,11 @@ func (r *queryResolver) ShareToken(ctx context.Context, token string, password *
}
}
return result, nil
if token.Password != nil && hashed_password != token.Password {
return nil, errors.New("unauthorized")
}
return token, nil
}
func (r *mutationResolver) ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error) {

View File

@@ -11,6 +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"
@@ -60,7 +61,7 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
return
}
} else {
// Check if photo is authorized with a share token
token := r.URL.Query().Get("token")
if token == "" {
w.WriteHeader(http.StatusForbidden)
@@ -78,6 +79,23 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
return
}
// 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 && photo.AlbumId != *shareToken.AlbumID {
// Check child albums
row := db.QueryRow(`

View File

@@ -3,13 +3,18 @@ import PropTypes from 'prop-types'
let imageCache = {}
export async function fetchProtectedImage(src, { signal } = { signal: null }) {
export async function fetchProtectedImage(
src,
{ signal, headers: customHeaders } = { signal: null, headers: null }
) {
if (src) {
if (imageCache[src]) {
return imageCache[src]
}
let headers = {}
let headers = {
...customHeaders,
}
if (localStorage.getItem('token')) {
headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`
}
@@ -44,16 +49,27 @@ const ProtectedImage = ({ src, ...props }) => {
setImgSrc('')
const imgUrl = new URL(src)
const fetchHeaders = {}
if (localStorage.getItem('token') == null) {
// Get share token if not authorized
const token = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
if (token) {
imgUrl.searchParams.set('token', token[1])
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
}
}
}
fetchProtectedImage(imgUrl.href, { signal: fetchController.signal })
fetchProtectedImage(imgUrl.href, {
signal: fetchController.signal,
headers: fetchHeaders,
})
.then(newSrc => {
if (!canceled) {
setImgSrc(newSrc)