Improve HTTP header auth
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
0becb8a92d
commit
f88c574f3f
|
@ -8,19 +8,18 @@ export default class Session {
|
||||||
constructor(storage) {
|
constructor(storage) {
|
||||||
this.auth = false;
|
this.auth = false;
|
||||||
|
|
||||||
if(storage.getItem("session_storage") === "true") {
|
if (storage.getItem("session_storage") === "true") {
|
||||||
this.storage = window.sessionStorage;
|
this.storage = window.sessionStorage;
|
||||||
} else {
|
} else {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session_token = this.storage.getItem("session_token");
|
if (this.applyToken(this.storage.getItem("session_token"))) {
|
||||||
|
const userJson = this.storage.getItem("user");
|
||||||
|
this.user = userJson !== "undefined" ? new User(JSON.parse(userJson)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
const userJson = this.storage.getItem("user");
|
if (this.isUser()) {
|
||||||
|
|
||||||
this.user = userJson !== "undefined" ? new User(JSON.parse(userJson)) : null;
|
|
||||||
|
|
||||||
if(this.isUser()) {
|
|
||||||
this.auth = true;
|
this.auth = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,10 +35,21 @@ export default class Session {
|
||||||
this.storage = window.localStorage;
|
this.storage = window.localStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
setToken(token) {
|
applyToken(token) {
|
||||||
|
if (!token) {
|
||||||
|
this.deleteToken();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.session_token = token;
|
this.session_token = token;
|
||||||
this.storage.setItem("session_token", token);
|
|
||||||
Api.defaults.headers.common["X-Session-Token"] = token;
|
Api.defaults.headers.common["X-Session-Token"] = token;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setToken(token) {
|
||||||
|
this.storage.setItem("session_token", token);
|
||||||
|
return this.applyToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
getToken() {
|
getToken() {
|
||||||
|
@ -49,7 +59,7 @@ export default class Session {
|
||||||
deleteToken() {
|
deleteToken() {
|
||||||
this.session_token = null;
|
this.session_token = null;
|
||||||
this.storage.removeItem("session_token");
|
this.storage.removeItem("session_token");
|
||||||
Api.defaults.headers.common["X-Session-Token"] = "";
|
delete Api.defaults.headers.common["X-Session-Token"];
|
||||||
this.deleteUser();
|
this.deleteUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +118,7 @@ export default class Session {
|
||||||
login(email, password) {
|
login(email, password) {
|
||||||
this.deleteToken();
|
this.deleteToken();
|
||||||
|
|
||||||
return Api.post("session", { email: email, password: password }).then(
|
return Api.post("session", {email: email, password: password}).then(
|
||||||
(result) => {
|
(result) => {
|
||||||
this.setToken(result.data.token);
|
this.setToken(result.data.token);
|
||||||
this.setUser(new User(result.data.user));
|
this.setUser(new User(result.data.user));
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import Api from "common/api";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
console.log("SUBMIT");
|
// DO NOTHING
|
||||||
},
|
},
|
||||||
startImport() {
|
startImport() {
|
||||||
this.started = Date.now();
|
this.started = Date.now();
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
|
|
||||||
const ctx = this;
|
const ctx = this;
|
||||||
|
|
||||||
axios.post('/api/v1/import').then(function () {
|
Api.post('import').then(function () {
|
||||||
Event.publish("alert.success", "Import complete");
|
Event.publish("alert.success", "Import complete");
|
||||||
ctx.busy = false;
|
ctx.busy = false;
|
||||||
ctx.completed = 100;
|
ctx.completed = 100;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import Api from "common/api";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
console.log("SUBMIT");
|
// DO NOTHING
|
||||||
},
|
},
|
||||||
startIndexing() {
|
startIndexing() {
|
||||||
this.started = Date.now();
|
this.started = Date.now();
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
|
|
||||||
const ctx = this;
|
const ctx = this;
|
||||||
|
|
||||||
axios.post('/api/v1/index').then(function () {
|
Api.post('index').then(function () {
|
||||||
Event.publish("alert.success", "Indexing complete");
|
Event.publish("alert.success", "Indexing complete");
|
||||||
ctx.busy = false;
|
ctx.busy = false;
|
||||||
ctx.completed = 100;
|
ctx.completed = 100;
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import Api from "common/api";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit() {
|
submit() {
|
||||||
// console.log("SUBMIT");
|
// DO NOTHING
|
||||||
},
|
},
|
||||||
uploadDialog() {
|
uploadDialog() {
|
||||||
this.$refs.upload.click();
|
this.$refs.upload.click();
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
formData.append('files', file);
|
formData.append('files', file);
|
||||||
|
|
||||||
await axios.post('/api/v1/upload/' + ctx.started,
|
await Api.post('upload/' + ctx.started,
|
||||||
formData,
|
formData,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
this.indexing = true;
|
this.indexing = true;
|
||||||
const ctx = this;
|
const ctx = this;
|
||||||
|
|
||||||
axios.post('/api/v1/import/upload/' + this.started).then(function () {
|
Api.post('import/upload/' + this.started).then(function () {
|
||||||
Event.publish("alert.success", "Upload complete");
|
Event.publish("alert.success", "Upload complete");
|
||||||
ctx.busy = false;
|
ctx.busy = false;
|
||||||
ctx.indexing = false;
|
ctx.indexing = false;
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('common/session', () => {
|
||||||
it('should set, get and delete user', () => {
|
it('should set, get and delete user', () => {
|
||||||
const storage = window.localStorage;
|
const storage = window.localStorage;
|
||||||
const session = new Session(storage);
|
const session = new Session(storage);
|
||||||
assert.equal(session.user.ID, undefined);
|
assert.equal(session.user, null);
|
||||||
const values = {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Role: "admin"};
|
const values = {ID: 5, FirstName: "Max", LastName: "Last", Email: "test@test.com", Role: "admin"};
|
||||||
const user = new User(values);
|
const user = new User(values);
|
||||||
session.setUser(user);
|
session.setUser(user);
|
||||||
|
|
|
@ -48,6 +48,11 @@ type CreateAlbumParams struct {
|
||||||
// POST /api/v1/albums
|
// POST /api/v1/albums
|
||||||
func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/albums", func(c *gin.Context) {
|
router.POST("/albums", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var params CreateAlbumParams
|
var params CreateAlbumParams
|
||||||
|
|
||||||
if err := c.BindJSON(¶ms); err != nil {
|
if err := c.BindJSON(¶ms); err != nil {
|
||||||
|
@ -73,6 +78,11 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// uuid: string Album UUID
|
// uuid: string Album UUID
|
||||||
func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/albums/:uuid/like", func(c *gin.Context) {
|
router.POST("/albums/:uuid/like", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
album, err := search.FindAlbumByUUID(c.Param("uuid"))
|
album, err := search.FindAlbumByUUID(c.Param("uuid"))
|
||||||
|
@ -95,6 +105,11 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// uuid: string Album UUID
|
// uuid: string Album UUID
|
||||||
func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.DELETE("/albums/:uuid/like", func(c *gin.Context) {
|
router.DELETE("/albums/:uuid/like", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
album, err := search.FindAlbumByUUID(c.Param("uuid"))
|
album, err := search.FindAlbumByUUID(c.Param("uuid"))
|
||||||
|
|
|
@ -8,24 +8,24 @@ import (
|
||||||
|
|
||||||
func TestGetAlbums(t *testing.T) {
|
func TestGetAlbums(t *testing.T) {
|
||||||
t.Run("successful request", func(t *testing.T) {
|
t.Run("successful request", func(t *testing.T) {
|
||||||
app, router, ctx := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
GetAlbums(router, ctx)
|
GetAlbums(router, conf)
|
||||||
result := PerformRequest(app, "GET", "/api/v1/albums?count=10")
|
result := PerformRequest(app, "GET", "/api/v1/albums?count=10")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, result.Code)
|
assert.Equal(t, http.StatusOK, result.Code)
|
||||||
})
|
})
|
||||||
t.Run("invalid request", func(t *testing.T) {
|
t.Run("invalid request", func(t *testing.T) {
|
||||||
app, router, ctx := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
GetAlbums(router, ctx)
|
GetAlbums(router, conf)
|
||||||
result := PerformRequest(app, "GET", "/api/v1/albums?xxx=10")
|
result := PerformRequest(app, "GET", "/api/v1/albums?xxx=10")
|
||||||
t.Log(result.Body)
|
t.Log(result.Body)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, result.Code)
|
assert.Equal(t, http.StatusBadRequest, result.Code)
|
||||||
})
|
})
|
||||||
t.Run("invalid request", func(t *testing.T) {
|
t.Run("invalid request", func(t *testing.T) {
|
||||||
app, router, ctx := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
t.Log(router)
|
t.Log(router)
|
||||||
t.Log(ctx)
|
t.Log(conf)
|
||||||
result := PerformRequest(app, "GET", "/api/v1/albums?xxx=10")
|
result := PerformRequest(app, "GET", "/api/v1/albums?xxx=10")
|
||||||
t.Log(result.Body)
|
t.Log(result.Body)
|
||||||
|
|
||||||
|
@ -47,9 +47,9 @@ func TestLikeAlbum(t *testing.T) {
|
||||||
|
|
||||||
func TestDislikeAlbum(t *testing.T) {
|
func TestDislikeAlbum(t *testing.T) {
|
||||||
t.Run("dislike not existing album", func(t *testing.T) {
|
t.Run("dislike not existing album", func(t *testing.T) {
|
||||||
app, router, ctx := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
|
|
||||||
LikeAlbum(router, ctx)
|
LikeAlbum(router, conf)
|
||||||
|
|
||||||
result := PerformRequest(app, "DELETE", "/api/v1/albums/5678/like")
|
result := PerformRequest(app, "DELETE", "/api/v1/albums/5678/like")
|
||||||
t.Log(result.Body)
|
t.Log(result.Body)
|
||||||
|
|
|
@ -33,6 +33,11 @@ func Import(router *gin.RouterGroup, conf *config.Config) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
path := conf.ImportPath()
|
path := conf.ImportPath()
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,11 @@ func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// slug: string Label slug name
|
// slug: string Label slug name
|
||||||
func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/labels/:slug/like", func(c *gin.Context) {
|
router.POST("/labels/:slug/like", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
label, err := search.FindLabelBySlug(c.Param("slug"))
|
label, err := search.FindLabelBySlug(c.Param("slug"))
|
||||||
|
@ -66,6 +71,11 @@ func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// slug: string Label slug name
|
// slug: string Label slug name
|
||||||
func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.DELETE("/labels/:slug/like", func(c *gin.Context) {
|
router.DELETE("/labels/:slug/like", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
label, err := search.FindLabelBySlug(c.Param("slug"))
|
label, err := search.FindLabelBySlug(c.Param("slug"))
|
||||||
|
|
|
@ -58,6 +58,11 @@ func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// id: int Photo ID as returned by the API
|
// id: int Photo ID as returned by the API
|
||||||
func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/photos/:id/like", func(c *gin.Context) {
|
router.POST("/photos/:id/like", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
photoID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
photoID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
|
||||||
|
@ -87,6 +92,11 @@ func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// id: int Photo ID as returned by the API
|
// id: int Photo ID as returned by the API
|
||||||
func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.DELETE("/photos/:id/like", func(c *gin.Context) {
|
router.DELETE("/photos/:id/like", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
|
||||||
|
|
|
@ -58,14 +58,18 @@ func DeleteSession(router *gin.RouterGroup, conf *config.Config) {
|
||||||
|
|
||||||
// Returns true, if user doesn't have a valid session token
|
// Returns true, if user doesn't have a valid session token
|
||||||
func Unauthorized(c *gin.Context, conf *config.Config) bool {
|
func Unauthorized(c *gin.Context, conf *config.Config) bool {
|
||||||
|
// Always return false if site is public
|
||||||
if conf.Public() {
|
if conf.Public() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get session token from HTTP header
|
||||||
token := c.GetHeader("X-Session-Token")
|
token := c.GetHeader("X-Session-Token")
|
||||||
gc := conf.Cache()
|
log.Debugf("X-Session-Token: %s", token)
|
||||||
|
|
||||||
|
// Check if session token is valid
|
||||||
|
gc := conf.Cache()
|
||||||
_, found := gc.Get(token)
|
_, found := gc.Get(token)
|
||||||
|
|
||||||
return found
|
return !found
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,11 @@ import (
|
||||||
// GET /api/v1/settings
|
// GET /api/v1/settings
|
||||||
func GetSettings(router *gin.RouterGroup, conf *config.Config) {
|
func GetSettings(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.GET("/settings", func(c *gin.Context) {
|
router.GET("/settings", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
result := conf.Settings()
|
result := conf.Settings()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
c.JSON(http.StatusOK, result)
|
||||||
|
@ -19,6 +24,11 @@ func GetSettings(router *gin.RouterGroup, conf *config.Config) {
|
||||||
// POST /api/v1/settings
|
// POST /api/v1/settings
|
||||||
func SaveSettings(router *gin.RouterGroup, conf *config.Config) {
|
func SaveSettings(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/settings", func(c *gin.Context) {
|
router.POST("/settings", func(c *gin.Context) {
|
||||||
|
if Unauthorized(c, conf) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "saved"})
|
c.JSON(http.StatusOK, gin.H{"message": "saved"})
|
||||||
|
|
|
@ -33,6 +33,8 @@ func NewTestParams() *Params {
|
||||||
testDataPath := testDataPath(assetsPath)
|
testDataPath := testDataPath(assetsPath)
|
||||||
|
|
||||||
c := &Params{
|
c := &Params{
|
||||||
|
Public: true,
|
||||||
|
ReadOnly: false,
|
||||||
DarktableBin: "/usr/bin/darktable-cli",
|
DarktableBin: "/usr/bin/darktable-cli",
|
||||||
AssetsPath: assetsPath,
|
AssetsPath: assetsPath,
|
||||||
CachePath: testDataPath + "/cache",
|
CachePath: testDataPath + "/cache",
|
||||||
|
|
Loading…
Reference in a new issue