Improve HTTP header auth

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2019-11-12 05:49:10 +01:00
parent 0becb8a92d
commit f88c574f3f
13 changed files with 98 additions and 32 deletions

View file

@ -8,19 +8,18 @@ export default class Session {
constructor(storage) {
this.auth = false;
if(storage.getItem("session_storage") === "true") {
if (storage.getItem("session_storage") === "true") {
this.storage = window.sessionStorage;
} else {
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");
this.user = userJson !== "undefined" ? new User(JSON.parse(userJson)) : null;
if(this.isUser()) {
if (this.isUser()) {
this.auth = true;
}
}
@ -36,10 +35,21 @@ export default class Session {
this.storage = window.localStorage;
}
setToken(token) {
applyToken(token) {
if (!token) {
this.deleteToken();
return false;
}
this.session_token = token;
this.storage.setItem("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() {
@ -49,7 +59,7 @@ export default class Session {
deleteToken() {
this.session_token = null;
this.storage.removeItem("session_token");
Api.defaults.headers.common["X-Session-Token"] = "";
delete Api.defaults.headers.common["X-Session-Token"];
this.deleteUser();
}
@ -108,7 +118,7 @@ export default class Session {
login(email, password) {
this.deleteToken();
return Api.post("session", { email: email, password: password }).then(
return Api.post("session", {email: email, password: password}).then(
(result) => {
this.setToken(result.data.token);
this.setUser(new User(result.data.user));

View file

@ -26,7 +26,7 @@
</template>
<script>
import axios from "axios";
import Api from "common/api";
import Event from "pubsub-js";
export default {
@ -40,7 +40,7 @@
},
methods: {
submit() {
console.log("SUBMIT");
// DO NOTHING
},
startImport() {
this.started = Date.now();
@ -51,7 +51,7 @@
const ctx = this;
axios.post('/api/v1/import').then(function () {
Api.post('import').then(function () {
Event.publish("alert.success", "Import complete");
ctx.busy = false;
ctx.completed = 100;

View file

@ -26,7 +26,7 @@
</template>
<script>
import axios from "axios";
import Api from "common/api";
import Event from "pubsub-js";
export default {
@ -40,7 +40,7 @@
},
methods: {
submit() {
console.log("SUBMIT");
// DO NOTHING
},
startIndexing() {
this.started = Date.now();
@ -51,7 +51,7 @@
const ctx = this;
axios.post('/api/v1/index').then(function () {
Api.post('index').then(function () {
Event.publish("alert.success", "Indexing complete");
ctx.busy = false;
ctx.completed = 100;

View file

@ -29,7 +29,7 @@
</template>
<script>
import axios from "axios";
import Api from "common/api";
import Event from "pubsub-js";
export default {
@ -48,7 +48,7 @@
},
methods: {
submit() {
// console.log("SUBMIT");
// DO NOTHING
},
uploadDialog() {
this.$refs.upload.click();
@ -80,7 +80,7 @@
formData.append('files', file);
await axios.post('/api/v1/upload/' + ctx.started,
await Api.post('upload/' + ctx.started,
formData,
{
headers: {
@ -99,7 +99,7 @@
this.indexing = true;
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");
ctx.busy = false;
ctx.indexing = false;

View file

@ -35,7 +35,7 @@ describe('common/session', () => {
it('should set, get and delete user', () => {
const storage = window.localStorage;
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 user = new User(values);
session.setUser(user);

View file

@ -48,6 +48,11 @@ type CreateAlbumParams struct {
// POST /api/v1/albums
func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
router.POST("/albums", func(c *gin.Context) {
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
var params CreateAlbumParams
if err := c.BindJSON(&params); err != nil {
@ -73,6 +78,11 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
// uuid: string Album UUID
func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
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())
album, err := search.FindAlbumByUUID(c.Param("uuid"))
@ -95,6 +105,11 @@ func LikeAlbum(router *gin.RouterGroup, conf *config.Config) {
// uuid: string Album UUID
func DislikeAlbum(router *gin.RouterGroup, conf *config.Config) {
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())
album, err := search.FindAlbumByUUID(c.Param("uuid"))

View file

@ -8,24 +8,24 @@ import (
func TestGetAlbums(t *testing.T) {
t.Run("successful request", func(t *testing.T) {
app, router, ctx := NewApiTest()
GetAlbums(router, ctx)
app, router, conf := NewApiTest()
GetAlbums(router, conf)
result := PerformRequest(app, "GET", "/api/v1/albums?count=10")
assert.Equal(t, http.StatusOK, result.Code)
})
t.Run("invalid request", func(t *testing.T) {
app, router, ctx := NewApiTest()
GetAlbums(router, ctx)
app, router, conf := NewApiTest()
GetAlbums(router, conf)
result := PerformRequest(app, "GET", "/api/v1/albums?xxx=10")
t.Log(result.Body)
assert.Equal(t, http.StatusBadRequest, result.Code)
})
t.Run("invalid request", func(t *testing.T) {
app, router, ctx := NewApiTest()
app, router, conf := NewApiTest()
t.Log(router)
t.Log(ctx)
t.Log(conf)
result := PerformRequest(app, "GET", "/api/v1/albums?xxx=10")
t.Log(result.Body)
@ -47,9 +47,9 @@ func TestLikeAlbum(t *testing.T) {
func TestDislikeAlbum(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")
t.Log(result.Body)

View file

@ -33,6 +33,11 @@ func Import(router *gin.RouterGroup, conf *config.Config) {
return
}
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
start := time.Now()
path := conf.ImportPath()

View file

@ -44,6 +44,11 @@ func GetLabels(router *gin.RouterGroup, conf *config.Config) {
// slug: string Label slug name
func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
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())
label, err := search.FindLabelBySlug(c.Param("slug"))
@ -66,6 +71,11 @@ func LikeLabel(router *gin.RouterGroup, conf *config.Config) {
// slug: string Label slug name
func DislikeLabel(router *gin.RouterGroup, conf *config.Config) {
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())
label, err := search.FindLabelBySlug(c.Param("slug"))

View file

@ -58,6 +58,11 @@ func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
// id: int Photo ID as returned by the API
func LikePhoto(router *gin.RouterGroup, conf *config.Config) {
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())
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
func DislikePhoto(router *gin.RouterGroup, conf *config.Config) {
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())
id, err := strconv.ParseUint(c.Param("id"), 10, 64)

View file

@ -58,14 +58,18 @@ func DeleteSession(router *gin.RouterGroup, conf *config.Config) {
// Returns true, if user doesn't have a valid session token
func Unauthorized(c *gin.Context, conf *config.Config) bool {
// Always return false if site is public
if conf.Public() {
return false
}
// Get session token from HTTP header
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)
return found
return !found
}

View file

@ -10,6 +10,11 @@ import (
// GET /api/v1/settings
func GetSettings(router *gin.RouterGroup, conf *config.Config) {
router.GET("/settings", func(c *gin.Context) {
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
result := conf.Settings()
c.JSON(http.StatusOK, result)
@ -19,6 +24,11 @@ func GetSettings(router *gin.RouterGroup, conf *config.Config) {
// POST /api/v1/settings
func SaveSettings(router *gin.RouterGroup, conf *config.Config) {
router.POST("/settings", func(c *gin.Context) {
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
// TODO
c.JSON(http.StatusOK, gin.H{"message": "saved"})

View file

@ -33,6 +33,8 @@ func NewTestParams() *Params {
testDataPath := testDataPath(assetsPath)
c := &Params{
Public: true,
ReadOnly: false,
DarktableBin: "/usr/bin/darktable-cli",
AssetsPath: assetsPath,
CachePath: testDataPath + "/cache",