These changes ensure that the new (SHA256) session ID is returned in the "session_id" field, so that developers have time to update their client implementations to use the new "access_token" field. Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
0d2f8be522
commit
f8e0615cc8
|
@ -47,7 +47,7 @@ const Api = Axios.create({
|
|||
baseURL: c.apiUri,
|
||||
headers: {
|
||||
common: {
|
||||
"X-Session-ID": window.localStorage.getItem("authToken"),
|
||||
"X-Auth-Token": window.localStorage.getItem("authToken"),
|
||||
"X-Client-Uri": c.jsUri,
|
||||
"X-Client-Version": c.version,
|
||||
},
|
||||
|
|
|
@ -28,7 +28,7 @@ import Event from "pubsub-js";
|
|||
import User from "model/user";
|
||||
import Socket from "websocket.js";
|
||||
|
||||
const RequestHeader = "X-Session-ID";
|
||||
const RequestHeader = "X-Auth-Token";
|
||||
const PublicSessionID = "a9b8ff820bf40ab451910f8bbfe401b2432446693aa539538fbd2399560a722f";
|
||||
const PublicAuthToken = "234200000000000000000000000000000000000000000000";
|
||||
const LoginPage = "login";
|
||||
|
@ -209,13 +209,15 @@ export default class Session {
|
|||
return;
|
||||
}
|
||||
|
||||
if (resp.data.id) {
|
||||
this.setId(resp.data.id);
|
||||
if (resp.data.session_id) {
|
||||
this.setId(resp.data.session_id);
|
||||
}
|
||||
|
||||
if (resp.data.access_token) {
|
||||
this.setAuthToken(resp.data.access_token);
|
||||
} else if (resp.data.id) {
|
||||
// TODO: "id" field is deprecated! Clients should now use "access_token" instead.
|
||||
// see https://github.com/photoprism/photoprism/commit/0d2f8be522dbf0a051ae6ef78abfc9efded0082d
|
||||
this.setAuthToken(resp.data.id);
|
||||
}
|
||||
|
||||
|
|
|
@ -133,8 +133,9 @@ Mock.onDelete("api/v1/photos/pqbemz8276mhtobh/label/12345").reply(
|
|||
Mock.onPost("api/v1/session").reply(
|
||||
200,
|
||||
{
|
||||
id: "5aa770f2a1ef431628d9f17bdf82a0d16865e99d4a1ddd9356e1aabfe6464683",
|
||||
session_id: "5aa770f2a1ef431628d9f17bdf82a0d16865e99d4a1ddd9356e1aabfe6464683",
|
||||
access_token: "999900000000000000000000000000000000000000000000",
|
||||
token_type: "Bearer",
|
||||
provider: "test",
|
||||
data: { token: "123token" },
|
||||
user: { ID: 1, UID: "urjysof3b9v7lgex", Name: "test", Email: "test@test.com" },
|
||||
|
@ -145,8 +146,9 @@ Mock.onPost("api/v1/session").reply(
|
|||
Mock.onGet("api/v1/session/a9b8ff820bf40ab451910f8bbfe401b2432446693aa539538fbd2399560a722f").reply(
|
||||
200,
|
||||
{
|
||||
id: "a9b8ff820bf40ab451910f8bbfe401b2432446693aa539538fbd2399560a722f",
|
||||
session_id: "a9b8ff820bf40ab451910f8bbfe401b2432446693aa539538fbd2399560a722f",
|
||||
access_token: "234200000000000000000000000000000000000000000000",
|
||||
token_type: "Bearer",
|
||||
provider: "public",
|
||||
data: { token: "123token" },
|
||||
user: { ID: 1, UID: "urjysof3b9v7lgex", Name: "test", Email: "test@test.com" },
|
||||
|
@ -157,8 +159,9 @@ Mock.onGet("api/v1/session/a9b8ff820bf40ab451910f8bbfe401b2432446693aa539538fbd2
|
|||
Mock.onGet("api/v1/session/5aa770f2a1ef431628d9f17bdf82a0d16865e99d4a1ddd9356e1aabfe6464683").reply(
|
||||
200,
|
||||
{
|
||||
id: "5aa770f2a1ef431628d9f17bdf82a0d16865e99d4a1ddd9356e1aabfe6464683",
|
||||
session_id: "5aa770f2a1ef431628d9f17bdf82a0d16865e99d4a1ddd9356e1aabfe6464683",
|
||||
access_token: "999900000000000000000000000000000000000000000000",
|
||||
token_type: "Bearer",
|
||||
provider: "test",
|
||||
data: { token: "123token" },
|
||||
user: { ID: 1, UID: "urjysof3b9v7lgex", Name: "test", Email: "test@test.com" },
|
||||
|
|
2
go.mod
2
go.mod
|
@ -52,7 +52,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
|
||||
golang.org/x/image v0.14.0
|
||||
golang.org/x/image v0.15.0
|
||||
)
|
||||
|
||||
require github.com/olekukonko/tablewriter v0.0.5
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1297,8 +1297,8 @@ golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmI
|
|||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
||||
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
|
@ -237,7 +237,7 @@ func DeleteAlbum(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
// PublishAlbumEvent(EntityDeleted, uid, c)
|
||||
// PublishAlbumEvent(StatusDeleted, uid, c)
|
||||
|
||||
UpdateClientConfig()
|
||||
|
||||
|
@ -250,11 +250,10 @@ func DeleteAlbum(router *gin.RouterGroup) {
|
|||
|
||||
// LikeAlbum sets the favorite flag for an album.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Album UID
|
||||
//
|
||||
// POST /api/v1/albums/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Album UID
|
||||
func LikeAlbum(router *gin.RouterGroup) {
|
||||
router.POST("/albums/:uid/like", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAlbums, acl.ActionUpdate)
|
||||
|
@ -287,7 +286,7 @@ func LikeAlbum(router *gin.RouterGroup) {
|
|||
|
||||
UpdateClientConfig()
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
PublishAlbumEvent(StatusUpdated, uid, c)
|
||||
|
||||
// Update album YAML backup.
|
||||
SaveAlbumAsYaml(a)
|
||||
|
@ -298,11 +297,10 @@ func LikeAlbum(router *gin.RouterGroup) {
|
|||
|
||||
// DislikeAlbum removes the favorite flag from an album.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Album UID
|
||||
//
|
||||
// DELETE /api/v1/albums/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Album UID
|
||||
func DislikeAlbum(router *gin.RouterGroup) {
|
||||
router.DELETE("/albums/:uid/like", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceAlbums, acl.ActionUpdate)
|
||||
|
@ -335,7 +333,7 @@ func DislikeAlbum(router *gin.RouterGroup) {
|
|||
|
||||
UpdateClientConfig()
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
PublishAlbumEvent(StatusUpdated, uid, c)
|
||||
|
||||
// Update album YAML backup.
|
||||
SaveAlbumAsYaml(a)
|
||||
|
@ -402,7 +400,7 @@ func CloneAlbums(router *gin.RouterGroup) {
|
|||
if len(added) > 0 {
|
||||
event.SuccessMsg(i18n.MsgSelectionAddedTo, clean.Log(a.Title()))
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
PublishAlbumEvent(StatusUpdated, a.AlbumUID, c)
|
||||
|
||||
// Update album YAML backup.
|
||||
SaveAlbumAsYaml(a)
|
||||
|
@ -473,7 +471,7 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
|
|||
|
||||
RemoveFromAlbumCoverCache(a.AlbumUID)
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
PublishAlbumEvent(StatusUpdated, a.AlbumUID, c)
|
||||
|
||||
// Update album YAML backup.
|
||||
SaveAlbumAsYaml(a)
|
||||
|
@ -537,7 +535,7 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
|
|||
|
||||
RemoveFromAlbumCoverCache(a.AlbumUID)
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
PublishAlbumEvent(StatusUpdated, a.AlbumUID, c)
|
||||
|
||||
// Update album YAML backup.
|
||||
SaveAlbumAsYaml(a)
|
||||
|
|
|
@ -8,17 +8,23 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/search"
|
||||
)
|
||||
|
||||
// EntityEvent represents an entity event type.
|
||||
type EntityEvent string
|
||||
// Event represents an api event type.
|
||||
type Event string
|
||||
|
||||
const (
|
||||
EntityUpdated EntityEvent = "updated"
|
||||
EntityCreated EntityEvent = "created"
|
||||
EntityDeleted EntityEvent = "deleted"
|
||||
StatusCreated Event = "created"
|
||||
StatusUpdated Event = "updated"
|
||||
StatusDeleted Event = "deleted"
|
||||
StatusSuccess Event = "success"
|
||||
)
|
||||
|
||||
// String returns the event type as string.
|
||||
func (ev Event) String() string {
|
||||
return string(ev)
|
||||
}
|
||||
|
||||
// PublishPhotoEvent publishes updated photo data after changes have been made.
|
||||
func PublishPhotoEvent(ev EntityEvent, uid string, c *gin.Context) {
|
||||
func PublishPhotoEvent(ev Event, uid string, c *gin.Context) {
|
||||
if result, _, err := search.Photos(form.SearchPhotos{UID: uid, Merged: true}); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "%s photo %s", "%s"}, AuthToken(c), string(ev), uid, err)
|
||||
} else {
|
||||
|
@ -27,7 +33,7 @@ func PublishPhotoEvent(ev EntityEvent, uid string, c *gin.Context) {
|
|||
}
|
||||
|
||||
// PublishAlbumEvent publishes updated album data after changes have been made.
|
||||
func PublishAlbumEvent(ev EntityEvent, uid string, c *gin.Context) {
|
||||
func PublishAlbumEvent(ev Event, uid string, c *gin.Context) {
|
||||
f := form.SearchAlbums{UID: uid}
|
||||
if result, err := search.Albums(f); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "%s album %s", "%s"}, AuthToken(c), string(ev), uid, err)
|
||||
|
@ -37,7 +43,7 @@ func PublishAlbumEvent(ev EntityEvent, uid string, c *gin.Context) {
|
|||
}
|
||||
|
||||
// PublishLabelEvent publishes updated label data after changes have been made.
|
||||
func PublishLabelEvent(ev EntityEvent, uid string, c *gin.Context) {
|
||||
func PublishLabelEvent(ev Event, uid string, c *gin.Context) {
|
||||
f := form.SearchLabels{UID: uid}
|
||||
if result, err := search.Labels(f); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "%s label %s", "%s"}, AuthToken(c), string(ev), uid, err)
|
||||
|
@ -47,7 +53,7 @@ func PublishLabelEvent(ev EntityEvent, uid string, c *gin.Context) {
|
|||
}
|
||||
|
||||
// PublishSubjectEvent publishes updated subject data after changes have been made.
|
||||
func PublishSubjectEvent(ev EntityEvent, uid string, c *gin.Context) {
|
||||
func PublishSubjectEvent(ev Event, uid string, c *gin.Context) {
|
||||
f := form.SearchSubjects{UID: uid}
|
||||
if result, err := search.Subjects(f); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "%s subject %s", "%s"}, AuthToken(c), string(ev), uid, err)
|
|
@ -8,8 +8,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
// AuthToken returns the client authentication token from the request context,
|
||||
|
@ -20,12 +21,14 @@ func AuthToken(c *gin.Context) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// First check the X-Session-ID header for an existing ID.
|
||||
if id := clean.ID(c.GetHeader(header.SessionID)); id != "" {
|
||||
return id
|
||||
// First check the "X-Auth-Token" and "X-Session-ID" headers for an auth token.
|
||||
if token := c.GetHeader(header.AuthToken); token != "" {
|
||||
return clean.ID(token)
|
||||
} else if id := c.GetHeader(header.SessionID); id != "" {
|
||||
return clean.ID(id)
|
||||
}
|
||||
|
||||
// Otherwise, return the bearer token, if any.
|
||||
// Otherwise, the bearer token from the authorization request header is returned.
|
||||
return BearerToken(c)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
func TestAuthToken(t *testing.T) {
|
||||
|
@ -41,7 +41,7 @@ func TestAuthToken(t *testing.T) {
|
|||
bearerToken := BearerToken(c)
|
||||
assert.Equal(t, authToken, bearerToken)
|
||||
})
|
||||
t.Run("X-Session-ID", func(t *testing.T) {
|
||||
t.Run("Header", func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
@ -50,7 +50,7 @@ func TestAuthToken(t *testing.T) {
|
|||
}
|
||||
|
||||
// Add authorization header.
|
||||
c.Request.Header.Add(header.SessionID, "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0")
|
||||
c.Request.Header.Add(header.AuthToken, "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0")
|
||||
|
||||
// Check result.
|
||||
authToken := AuthToken(c)
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
// AddCountHeader adds the actual result count to the response.
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
type CloseableResponseRecorder struct {
|
||||
|
|
|
@ -23,13 +23,12 @@ const (
|
|||
|
||||
// AlbumCover returns an album cover image.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string album uid
|
||||
// - token: string security token (see config)
|
||||
// - size: string thumb type, see photoprism.ThumbnailTypes
|
||||
//
|
||||
// GET /api/v1/albums/:uid/t/:token/:size
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string album uid
|
||||
// token: string security token (see config)
|
||||
// size: string thumb type, see photoprism.ThumbnailTypes
|
||||
func AlbumCover(router *gin.RouterGroup) {
|
||||
router.GET("/albums/:uid/t/:token/:size", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
|
@ -136,13 +135,12 @@ func AlbumCover(router *gin.RouterGroup) {
|
|||
|
||||
// LabelCover returns a label cover image.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string label uid
|
||||
// - token: string security token (see config)
|
||||
// - size: string thumb type, see photoprism.ThumbnailTypes
|
||||
//
|
||||
// GET /api/v1/labels/:uid/t/:token/:size
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string label uid
|
||||
// token: string security token (see config)
|
||||
// size: string thumb type, see photoprism.ThumbnailTypes
|
||||
func LabelCover(router *gin.RouterGroup) {
|
||||
router.GET("/labels/:uid/t/:token/:size", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
|
|
|
@ -35,11 +35,10 @@ func DownloadName(c *gin.Context) customize.DownloadName {
|
|||
|
||||
// GetDownload returns the raw file data.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - hash: string The file hash as returned by the files/photos endpoint
|
||||
//
|
||||
// GET /api/v1/dl/:hash
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// hash: string The file hash as returned by the files/photos endpoint
|
||||
func GetDownload(router *gin.RouterGroup) {
|
||||
router.GET("/dl/:hash", func(c *gin.Context) {
|
||||
if InvalidDownloadToken(c) {
|
||||
|
|
|
@ -16,12 +16,12 @@ import (
|
|||
)
|
||||
|
||||
// DeleteFile removes a file from storage.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Photo UID as returned by the API
|
||||
// - file_uid: string File UID as returned by the API
|
||||
//
|
||||
// DELETE /api/v1/photos/:uid/files/:file_uid
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Photo UID as returned by the API
|
||||
// file_uid: string File UID as returned by the API
|
||||
func DeleteFile(router *gin.RouterGroup) {
|
||||
router.DELETE("/photos/:uid/files/:file_uid", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceFiles, acl.ActionDelete)
|
||||
|
@ -88,7 +88,7 @@ func DeleteFile(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
// Notify clients by publishing events.
|
||||
PublishPhotoEvent(EntityUpdated, photoUid, c)
|
||||
PublishPhotoEvent(StatusUpdated, photoUid, c)
|
||||
|
||||
// Show translated success message.
|
||||
event.SuccessMsg(i18n.MsgFileDeleted)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
|
@ -14,12 +15,12 @@ import (
|
|||
)
|
||||
|
||||
// ChangeFileOrientation changes the orientation of a file.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Photo UID as returned by the API
|
||||
// - file_uid: string File UID as returned by the API
|
||||
//
|
||||
// PUT /api/v1/photos/:uid/files/:file_uid/orientation
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Photo UID as returned by the API
|
||||
// file_uid: string File UID as returned by the API
|
||||
func ChangeFileOrientation(router *gin.RouterGroup) {
|
||||
router.PUT("/photos/:uid/files/:file_uid/orientation", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceFiles, acl.ActionUpdate)
|
||||
|
@ -99,7 +100,7 @@ func ChangeFileOrientation(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, m.PhotoUID, c)
|
||||
PublishPhotoEvent(StatusUpdated, m.PhotoUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, p)
|
||||
})
|
||||
|
|
|
@ -12,9 +12,10 @@ import (
|
|||
|
||||
// GetFile returns file details as JSON.
|
||||
//
|
||||
// GET /api/v1/files/:hash
|
||||
// Params:
|
||||
// Request Parameters:
|
||||
// - hash (string) SHA-1 hash of the file
|
||||
//
|
||||
// GET /api/v1/files/:hash
|
||||
func GetFile(router *gin.RouterGroup) {
|
||||
router.GET("/files/:hash", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceFiles, acl.ActionView)
|
||||
|
|
|
@ -21,13 +21,12 @@ const (
|
|||
|
||||
// FolderCover returns a folder cover image.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string folder uid
|
||||
// - token: string url security token, see config
|
||||
// - size: string thumb type, see thumb.Sizes
|
||||
//
|
||||
// GET /api/v1/folders/t/:hash/:token/:size
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string folder uid
|
||||
// token: string url security token, see config
|
||||
// size: string thumb type, see thumb.Sizes
|
||||
func FolderCover(router *gin.RouterGroup) {
|
||||
router.GET("/folders/t/:uid/:token/:size", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
|
|
|
@ -151,7 +151,7 @@ func StartImport(router *gin.RouterGroup) {
|
|||
event.Publish("index.completed", eventData)
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
PublishAlbumEvent(StatusUpdated, uid, c)
|
||||
}
|
||||
|
||||
// Update the user interface.
|
||||
|
|
|
@ -46,7 +46,7 @@ func UpdateLabel(router *gin.RouterGroup) {
|
|||
|
||||
event.SuccessMsg(i18n.MsgLabelSaved)
|
||||
|
||||
PublishLabelEvent(EntityUpdated, id, c)
|
||||
PublishLabelEvent(StatusUpdated, id, c)
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
})
|
||||
|
@ -54,11 +54,10 @@ func UpdateLabel(router *gin.RouterGroup) {
|
|||
|
||||
// LikeLabel flags a label as favorite.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Label UID
|
||||
//
|
||||
// POST /api/v1/labels/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Label UID
|
||||
func LikeLabel(router *gin.RouterGroup) {
|
||||
router.POST("/labels/:uid/like", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceLabels, acl.ActionUpdate)
|
||||
|
@ -86,7 +85,7 @@ func LikeLabel(router *gin.RouterGroup) {
|
|||
})
|
||||
}
|
||||
|
||||
PublishLabelEvent(EntityUpdated, id, c)
|
||||
PublishLabelEvent(StatusUpdated, id, c)
|
||||
|
||||
c.JSON(http.StatusOK, http.Response{})
|
||||
})
|
||||
|
@ -94,11 +93,10 @@ func LikeLabel(router *gin.RouterGroup) {
|
|||
|
||||
// DislikeLabel removes the favorite flag from a label.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Label UID
|
||||
//
|
||||
// DELETE /api/v1/labels/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Label UID
|
||||
func DislikeLabel(router *gin.RouterGroup) {
|
||||
router.DELETE("/labels/:uid/like", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceLabels, acl.ActionUpdate)
|
||||
|
@ -126,7 +124,7 @@ func DislikeLabel(router *gin.RouterGroup) {
|
|||
})
|
||||
}
|
||||
|
||||
PublishLabelEvent(EntityUpdated, id, c)
|
||||
PublishLabelEvent(StatusUpdated, id, c)
|
||||
|
||||
c.JSON(http.StatusOK, http.Response{})
|
||||
})
|
||||
|
|
|
@ -57,7 +57,7 @@ func UpdateLink(c *gin.Context) {
|
|||
|
||||
UpdateClientConfig()
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, link.ShareUID, c)
|
||||
PublishAlbumEvent(StatusUpdated, link.ShareUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, link)
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func DeleteLink(c *gin.Context) {
|
|||
|
||||
UpdateClientConfig()
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, link.ShareUID, c)
|
||||
PublishAlbumEvent(StatusUpdated, link.ShareUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, link)
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func CreateLink(c *gin.Context) {
|
|||
|
||||
UpdateClientConfig()
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, link.ShareUID, c)
|
||||
PublishAlbumEvent(StatusUpdated, link.ShareUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, link)
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ func CreateMarker(router *gin.RouterGroup) {
|
|||
log.Errorf("faces: %s (update photo title)", err)
|
||||
} else {
|
||||
// Publish updated photo entity.
|
||||
PublishPhotoEvent(EntityUpdated, file.PhotoUID, c)
|
||||
PublishPhotoEvent(StatusUpdated, file.PhotoUID, c)
|
||||
}
|
||||
|
||||
// Display success message.
|
||||
|
@ -174,11 +174,10 @@ func CreateMarker(router *gin.RouterGroup) {
|
|||
|
||||
// UpdateMarker updates an existing file area marker to assign faces or other subjects.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - marker_uid: string Marker UID as returned by the API
|
||||
//
|
||||
// PUT /api/v1/markers/:marker_uid
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// marker_uid: string Marker UID as returned by the API
|
||||
func UpdateMarker(router *gin.RouterGroup) {
|
||||
router.PUT("/markers/:marker_uid", func(c *gin.Context) {
|
||||
// Abort if workers runs less than once per hour.
|
||||
|
@ -253,7 +252,7 @@ func UpdateMarker(router *gin.RouterGroup) {
|
|||
log.Errorf("faces: %s (update photo title)", err)
|
||||
} else {
|
||||
// Notify clients.
|
||||
PublishPhotoEvent(EntityUpdated, file.PhotoUID, c)
|
||||
PublishPhotoEvent(StatusUpdated, file.PhotoUID, c)
|
||||
}
|
||||
|
||||
// Display success message.
|
||||
|
@ -266,13 +265,12 @@ func UpdateMarker(router *gin.RouterGroup) {
|
|||
|
||||
// ClearMarkerSubject removes an existing marker subject association.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Photo UID as returned by the API
|
||||
// - file_uid: string File UID as returned by the API
|
||||
// - id: int Marker ID as returned by the API
|
||||
//
|
||||
// DELETE /api/v1/markers/:marker_uid/subject
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Photo UID as returned by the API
|
||||
// file_uid: string File UID as returned by the API
|
||||
// id: int Marker ID as returned by the API
|
||||
func ClearMarkerSubject(router *gin.RouterGroup) {
|
||||
router.DELETE("/markers/:marker_uid/subject", func(c *gin.Context) {
|
||||
// Abort if workers runs less than once per hour.
|
||||
|
@ -314,7 +312,7 @@ func ClearMarkerSubject(router *gin.RouterGroup) {
|
|||
log.Errorf("faces: %s (update photo title)", err)
|
||||
} else {
|
||||
// Notify clients.
|
||||
PublishPhotoEvent(EntityUpdated, file.PhotoUID, c)
|
||||
PublishPhotoEvent(StatusUpdated, file.PhotoUID, c)
|
||||
}
|
||||
|
||||
event.SuccessMsg(i18n.MsgChangesSaved)
|
||||
|
|
|
@ -16,11 +16,12 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// AddPhotoLabel adds a label to a photo.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string PhotoUID as returned by the API
|
||||
//
|
||||
// POST /api/v1/photos/:uid/label
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string PhotoUID as returned by the API
|
||||
func AddPhotoLabel(router *gin.RouterGroup) {
|
||||
router.POST("/photos/:uid/label", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
@ -82,7 +83,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, c.Param("uid"), c)
|
||||
PublishPhotoEvent(StatusUpdated, c.Param("uid"), c)
|
||||
|
||||
event.Success("label updated")
|
||||
|
||||
|
@ -90,12 +91,13 @@ func AddPhotoLabel(router *gin.RouterGroup) {
|
|||
})
|
||||
}
|
||||
|
||||
// RemovePhotoLabel removes a label from a photo.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string PhotoUID as returned by the API
|
||||
// - id: int LabelId as returned by the API
|
||||
//
|
||||
// DELETE /api/v1/photos/:uid/label/:id
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// id: int LabelId as returned by the API
|
||||
func RemovePhotoLabel(router *gin.RouterGroup) {
|
||||
router.DELETE("/photos/:uid/label/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
@ -146,7 +148,7 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, clean.UID(c.Param("uid")), c)
|
||||
PublishPhotoEvent(StatusUpdated, clean.UID(c.Param("uid")), c)
|
||||
|
||||
event.Success("label removed")
|
||||
|
||||
|
@ -154,12 +156,13 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
|
|||
})
|
||||
}
|
||||
|
||||
// UpdatePhotoLabel changes a photo labels.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string PhotoUID as returned by the API
|
||||
// - id: int LabelId as returned by the API
|
||||
//
|
||||
// PUT /api/v1/photos/:uid/label/:id
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// id: int LabelId as returned by the API
|
||||
func UpdatePhotoLabel(router *gin.RouterGroup) {
|
||||
router.PUT("/photos/:uid/label/:id", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
@ -213,7 +216,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, clean.UID(c.Param("uid")), c)
|
||||
PublishPhotoEvent(StatusUpdated, clean.UID(c.Param("uid")), c)
|
||||
|
||||
event.Success("label saved")
|
||||
|
||||
|
|
|
@ -19,12 +19,11 @@ import (
|
|||
|
||||
// PhotoUnstack removes a file from an existing photo stack.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Photo UID as returned by the API
|
||||
// - file_uid: string File UID as returned by the API
|
||||
//
|
||||
// POST /api/v1/photos/:uid/files/:file_uid/unstack
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Photo UID as returned by the API
|
||||
// file_uid: string File UID as returned by the API
|
||||
func PhotoUnstack(router *gin.RouterGroup) {
|
||||
router.POST("/photos/:uid/files/:file_uid/unstack", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
@ -194,8 +193,8 @@ func PhotoUnstack(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
// Notify clients by publishing events.
|
||||
PublishPhotoEvent(EntityCreated, newPhoto.PhotoUID, c)
|
||||
PublishPhotoEvent(EntityUpdated, stackPhoto.PhotoUID, c)
|
||||
PublishPhotoEvent(StatusCreated, newPhoto.PhotoUID, c)
|
||||
PublishPhotoEvent(StatusUpdated, stackPhoto.PhotoUID, c)
|
||||
|
||||
event.SuccessMsg(i18n.MsgFileUnstacked)
|
||||
|
||||
|
|
|
@ -38,9 +38,10 @@ func SavePhotoAsYaml(p entity.Photo) {
|
|||
|
||||
// GetPhoto returns photo details as JSON.
|
||||
//
|
||||
// Route : GET /api/v1/photos/:uid
|
||||
// Params:
|
||||
// Request Parameters:
|
||||
// - uid (string) PhotoUID as returned by the API
|
||||
//
|
||||
// GET /api/v1/photos/:uid
|
||||
func GetPhoto(router *gin.RouterGroup) {
|
||||
router.GET("/photos/:uid", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionView)
|
||||
|
@ -101,7 +102,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
|||
FlushCoverCache()
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, uid, c)
|
||||
PublishPhotoEvent(StatusUpdated, uid, c)
|
||||
|
||||
event.SuccessMsg(i18n.MsgChangesSaved)
|
||||
|
||||
|
@ -123,7 +124,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
|
|||
// GetPhotoDownload returns the primary file matching that belongs to the photo.
|
||||
//
|
||||
// Route :GET /api/v1/photos/:uid/dl
|
||||
// Params:
|
||||
// Request Parameters:
|
||||
// - uid (string) PhotoUID as returned by the API
|
||||
func GetPhotoDownload(router *gin.RouterGroup) {
|
||||
router.GET("/photos/:uid/dl", func(c *gin.Context) {
|
||||
|
@ -157,10 +158,10 @@ func GetPhotoDownload(router *gin.RouterGroup) {
|
|||
|
||||
// GetPhotoYaml returns photo details as YAML.
|
||||
//
|
||||
// GET /api/v1/photos/:uid/yaml
|
||||
// Params:
|
||||
// Request Parameters:
|
||||
// - uid: string PhotoUID as returned by the API
|
||||
//
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// GET /api/v1/photos/:uid/yaml
|
||||
func GetPhotoYaml(router *gin.RouterGroup) {
|
||||
router.GET("/photos/:uid/yaml", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.AccessAll)
|
||||
|
@ -193,10 +194,10 @@ func GetPhotoYaml(router *gin.RouterGroup) {
|
|||
|
||||
// ApprovePhoto marks a photo in review as approved.
|
||||
//
|
||||
// POST /api/v1/photos/:uid/approve
|
||||
// Params:
|
||||
// Request Parameters:
|
||||
// - uid: string PhotoUID as returned by the API
|
||||
//
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// POST /api/v1/photos/:uid/approve
|
||||
func ApprovePhoto(router *gin.RouterGroup) {
|
||||
router.POST("/photos/:uid/approve", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
@ -221,7 +222,7 @@ func ApprovePhoto(router *gin.RouterGroup) {
|
|||
|
||||
SavePhotoAsYaml(m)
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, id, c)
|
||||
PublishPhotoEvent(StatusUpdated, id, c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"photo": m})
|
||||
})
|
||||
|
@ -229,11 +230,11 @@ func ApprovePhoto(router *gin.RouterGroup) {
|
|||
|
||||
// PhotoPrimary sets the primary file for a photo.
|
||||
//
|
||||
// POST /photos/:uid/files/:file_uid/primary
|
||||
// Params:
|
||||
// Request Parameters:
|
||||
// - uid: string PhotoUID as returned by the API
|
||||
// - file_uid: string File UID as returned by the API
|
||||
//
|
||||
// uid: string PhotoUID as returned by the API
|
||||
// file_uid: string File UID as returned by the API
|
||||
// POST /photos/:uid/files/:file_uid/primary
|
||||
func PhotoPrimary(router *gin.RouterGroup) {
|
||||
router.POST("/photos/:uid/files/:file_uid/primary", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
@ -251,7 +252,7 @@ func PhotoPrimary(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishPhotoEvent(EntityUpdated, uid, c)
|
||||
PublishPhotoEvent(StatusUpdated, uid, c)
|
||||
|
||||
event.SuccessMsg(i18n.MsgChangesSaved)
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ func LikePhoto(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
SavePhotoAsYaml(m)
|
||||
PublishPhotoEvent(EntityUpdated, id, c)
|
||||
PublishPhotoEvent(StatusUpdated, id, c)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"photo": m})
|
||||
|
@ -85,7 +85,7 @@ func DislikePhoto(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
SavePhotoAsYaml(m)
|
||||
PublishPhotoEvent(EntityUpdated, id, c)
|
||||
PublishPhotoEvent(StatusUpdated, id, c)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"photo": m})
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
|
@ -27,39 +24,3 @@ func Session(authToken string) *entity.Session {
|
|||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// SessionResponse returns authentication response data based on the session and client config.
|
||||
func SessionResponse(authToken string, sess *entity.Session, conf config.ClientConfig) gin.H {
|
||||
if authToken == "" {
|
||||
return gin.H{
|
||||
"status": "ok",
|
||||
"id": sess.ID,
|
||||
"expires_in": sess.ExpiresIn(),
|
||||
"provider": sess.Provider().String(),
|
||||
"user": sess.User(),
|
||||
"data": sess.Data(),
|
||||
"config": conf,
|
||||
}
|
||||
} else {
|
||||
return gin.H{
|
||||
"status": "ok",
|
||||
"id": sess.ID,
|
||||
"access_token": authToken,
|
||||
"token_type": sess.AuthTokenType(),
|
||||
"expires_in": sess.ExpiresIn(),
|
||||
"provider": sess.Provider().String(),
|
||||
"user": sess.User(),
|
||||
"data": sess.Data(),
|
||||
"config": conf,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SessionDeleteResponse returns a confirmation response for deleted sessions.
|
||||
func SessionDeleteResponse(authToken string) gin.H {
|
||||
if authToken == "" {
|
||||
return gin.H{"status": "ok"}
|
||||
} else {
|
||||
return gin.H{"status": "ok", "id": authToken, "access_token": authToken}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func CreateSession(router *gin.RouterGroup) {
|
|||
sess := get.Session().Public()
|
||||
|
||||
// Response includes admin account data, session data, and client config values.
|
||||
response := SessionResponse(sess.AuthToken(), sess, conf.ClientPublic())
|
||||
response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientPublic())
|
||||
|
||||
// Return JSON response.
|
||||
c.JSON(http.StatusOK, response)
|
||||
|
@ -80,7 +80,7 @@ func CreateSession(router *gin.RouterGroup) {
|
|||
AddSessionHeader(c, sess.AuthToken())
|
||||
|
||||
// Response includes user data, session data, and client config values.
|
||||
response := SessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess))
|
||||
response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess))
|
||||
|
||||
// Return JSON response.
|
||||
c.JSON(sess.HttpStatus(), response)
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/session"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
@ -26,7 +25,8 @@ func DeleteSession(router *gin.RouterGroup) {
|
|||
AbortBadRequest(c)
|
||||
return
|
||||
} else if get.Config().Public() {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "running in public mode", "id": session.PublicAuthToken})
|
||||
// Return JSON response for confirmation.
|
||||
c.JSON(http.StatusOK, DeleteSessionResponse(id))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -58,17 +58,14 @@ func DeleteSession(router *gin.RouterGroup) {
|
|||
}
|
||||
}
|
||||
|
||||
// Delete session.
|
||||
// Delete session cache and database record.
|
||||
if err := get.Session().Delete(id); err != nil {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s"}, err)
|
||||
} else {
|
||||
event.AuditDebug([]string{ClientIP(c), "session deleted"})
|
||||
}
|
||||
|
||||
// Response includes the auth token for confirmation.
|
||||
response := SessionDeleteResponse(id)
|
||||
|
||||
// Return JSON response.
|
||||
c.JSON(http.StatusOK, response)
|
||||
// Return JSON response for confirmation.
|
||||
c.JSON(http.StatusOK, DeleteSessionResponse(id))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func GetSession(router *gin.RouterGroup) {
|
|||
AddSessionHeader(c, authToken)
|
||||
|
||||
// Response includes user data, session data, and client config values.
|
||||
response := SessionResponse(authToken, sess, get.Config().ClientSession(sess))
|
||||
response := GetSessionResponse(authToken, sess, get.Config().ClientSession(sess))
|
||||
|
||||
// Return JSON response.
|
||||
c.JSON(http.StatusOK, response)
|
||||
|
|
54
internal/api/session_response.go
Normal file
54
internal/api/session_response.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
// CreateSessionResponse returns the authentication response data for POST requests
|
||||
// based on the session and configuration.
|
||||
func CreateSessionResponse(authToken string, sess *entity.Session, conf config.ClientConfig) gin.H {
|
||||
return GetSessionResponse(authToken, sess, conf)
|
||||
}
|
||||
|
||||
// GetSessionResponse returns the authentication response data for GET requests
|
||||
// based on the session and configuration.
|
||||
func GetSessionResponse(authToken string, sess *entity.Session, conf config.ClientConfig) gin.H {
|
||||
if authToken == "" {
|
||||
return gin.H{
|
||||
"status": StatusSuccess,
|
||||
"session_id": sess.ID,
|
||||
"expires_in": sess.ExpiresIn(),
|
||||
"provider": sess.Provider().String(),
|
||||
"user": sess.User(),
|
||||
"data": sess.Data(),
|
||||
"config": conf,
|
||||
}
|
||||
} else {
|
||||
return gin.H{
|
||||
"status": StatusSuccess,
|
||||
// TODO: "id" field is deprecated! Clients should now use "access_token" instead.
|
||||
// see https://github.com/photoprism/photoprism/commit/0d2f8be522dbf0a051ae6ef78abfc9efded0082d
|
||||
"id": authToken,
|
||||
"session_id": sess.ID,
|
||||
"access_token": authToken,
|
||||
"token_type": sess.AuthTokenType(),
|
||||
"expires_in": sess.ExpiresIn(),
|
||||
"provider": sess.Provider().String(),
|
||||
"user": sess.User(),
|
||||
"data": sess.Data(),
|
||||
"config": conf,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSessionResponse returns a confirmation response for DELETE requests.
|
||||
func DeleteSessionResponse(id string) gin.H {
|
||||
if id == "" {
|
||||
return gin.H{"status": StatusDeleted}
|
||||
} else {
|
||||
return gin.H{"status": StatusDeleted, "session_id": id}
|
||||
}
|
||||
}
|
|
@ -28,11 +28,12 @@ func TestSessionResponse(t *testing.T) {
|
|||
conf := get.Config().ClientSession(sess)
|
||||
|
||||
// Create response in public mode.
|
||||
result := SessionResponse(sess.AuthToken(), sess, conf)
|
||||
result := GetSessionResponse(sess.AuthToken(), sess, conf)
|
||||
|
||||
// Check response.
|
||||
assert.Equal(t, "ok", result["status"])
|
||||
assert.Equal(t, sess.ID, result["id"])
|
||||
assert.Equal(t, StatusSuccess, result["status"])
|
||||
assert.Equal(t, sess.ID, result["session_id"])
|
||||
assert.Equal(t, sess.AuthToken(), result["id"])
|
||||
assert.Equal(t, sess.AuthToken(), result["access_token"])
|
||||
assert.Equal(t, sess.AuthTokenType(), result["token_type"])
|
||||
assert.Equal(t, sess.ExpiresIn(), result["expires_in"])
|
||||
|
@ -46,11 +47,12 @@ func TestSessionResponse(t *testing.T) {
|
|||
conf := get.Config().ClientSession(sess)
|
||||
|
||||
// Create response without auth token.
|
||||
result := SessionResponse("", sess, conf)
|
||||
result := GetSessionResponse("", sess, conf)
|
||||
|
||||
// Check response.
|
||||
assert.Equal(t, "ok", result["status"])
|
||||
assert.Equal(t, sess.ID, result["id"])
|
||||
assert.Equal(t, StatusSuccess, result["status"])
|
||||
assert.Equal(t, sess.ID, result["session_id"])
|
||||
assert.Nil(t, result["id"])
|
||||
assert.Nil(t, result["access_token"])
|
||||
assert.Nil(t, result["token_type"])
|
||||
assert.Equal(t, sess.ExpiresIn(), result["expires_in"])
|
||||
|
@ -70,9 +72,9 @@ func TestCreateSession(t *testing.T) {
|
|||
CreateSession(router)
|
||||
|
||||
r := PerformRequestWithBody(app, http.MethodPost, "/api/v1/session", `{"username": "admin", "password": "photoprism"}`)
|
||||
log.Debugf("BODY: %s", r.Body.String())
|
||||
val2 := gjson.Get(r.Body.String(), "user.Name")
|
||||
assert.Equal(t, "admin", val2.String())
|
||||
t.Logf("Response Body: %s", r.Body.String())
|
||||
userName := gjson.Get(r.Body.String(), "user.Name").String()
|
||||
assert.Equal(t, "admin", userName)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("BadRequest", func(t *testing.T) {
|
||||
|
@ -213,6 +215,9 @@ func TestGetSession(t *testing.T) {
|
|||
|
||||
t.Logf("Session ID: %s", authToken)
|
||||
r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/session/"+rnd.SessionID(authToken), authToken)
|
||||
t.Logf("Response Body: %s", r.Body.String())
|
||||
id := gjson.Get(r.Body.String(), "session_id").String()
|
||||
assert.Equal(t, rnd.SessionID(authToken), id)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
@ -263,6 +268,9 @@ func TestDeleteSession(t *testing.T) {
|
|||
authToken := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||
|
||||
r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), authToken)
|
||||
t.Logf("Response Body: %s", r.Body.String())
|
||||
id := gjson.Get(r.Body.String(), "session_id").String()
|
||||
assert.Equal(t, rnd.SessionID(authToken), id)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("AliceSessionAsBob", func(t *testing.T) {
|
||||
|
|
|
@ -94,11 +94,10 @@ func UpdateSubject(router *gin.RouterGroup) {
|
|||
|
||||
// LikeSubject flags a subject as favorite.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Subject UID
|
||||
//
|
||||
// POST /api/v1/subjects/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Subject UID
|
||||
func LikeSubject(router *gin.RouterGroup) {
|
||||
router.POST("/subjects/:uid/like", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePeople, acl.ActionUpdate)
|
||||
|
@ -120,7 +119,7 @@ func LikeSubject(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishSubjectEvent(EntityUpdated, uid, c)
|
||||
PublishSubjectEvent(StatusUpdated, uid, c)
|
||||
|
||||
c.JSON(http.StatusOK, http.Response{})
|
||||
})
|
||||
|
@ -128,11 +127,10 @@ func LikeSubject(router *gin.RouterGroup) {
|
|||
|
||||
// DislikeSubject removes the favorite flag from a subject.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - uid: string Subject UID
|
||||
//
|
||||
// DELETE /api/v1/subjects/:uid/like
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// uid: string Subject UID
|
||||
func DislikeSubject(router *gin.RouterGroup) {
|
||||
router.DELETE("/subjects/:uid/like", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePeople, acl.ActionUpdate)
|
||||
|
@ -154,7 +152,7 @@ func DislikeSubject(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
PublishSubjectEvent(EntityUpdated, uid, c)
|
||||
PublishSubjectEvent(StatusUpdated, uid, c)
|
||||
|
||||
c.JSON(http.StatusOK, http.Response{})
|
||||
})
|
||||
|
|
|
@ -18,13 +18,12 @@ import (
|
|||
|
||||
// GetThumb returns a thumbnail image matching the file hash, crop area, and type.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - thumb: string sha1 file hash plus optional crop area
|
||||
// - token: string url security token, see config
|
||||
// - size: string thumb type, see thumb.Sizes
|
||||
//
|
||||
// GET /api/v1/t/:thumb/:token/:size
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// thumb: string sha1 file hash plus optional crop area
|
||||
// token: string url security token, see config
|
||||
// size: string thumb type, see thumb.Sizes
|
||||
func GetThumb(router *gin.RouterGroup) {
|
||||
router.GET("/t/:thumb/:token/:size", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
|
|
|
@ -242,7 +242,7 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
|||
event.Publish("upload.completed", event.Data{"uid": opt.UID, "path": uploadPath, "seconds": elapsed})
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
PublishAlbumEvent(StatusUpdated, uid, c)
|
||||
}
|
||||
|
||||
// Update the user interface.
|
||||
|
|
|
@ -19,12 +19,11 @@ import (
|
|||
|
||||
// GetVideo streams video content.
|
||||
//
|
||||
// Request Parameters:
|
||||
// - hash: string The photo or video file hash as returned by the search API
|
||||
// - type: string Video format
|
||||
//
|
||||
// GET /api/v1/videos/:hash/:token/:type
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// hash: string The photo or video file hash as returned by the search API
|
||||
// type: string Video format
|
||||
func GetVideo(router *gin.RouterGroup) {
|
||||
router.GET("/videos/:hash/:token/:format", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
|
|
45
internal/api/websocket.go
Normal file
45
internal/api/websocket.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
// wsTimeout specifies the timeout duration for WebSocket connections.
|
||||
var wsTimeout = 90 * time.Second
|
||||
|
||||
// wsSubPerm specifies the permissions required to subscribe to a channel.
|
||||
var wsSubscribePerms = acl.Permissions{acl.ActionSubscribe}
|
||||
|
||||
// wsAuth maps connection IDs to specific users and session IDs.
|
||||
var wsAuth = struct {
|
||||
sid map[string]string
|
||||
rid map[string]string
|
||||
user map[string]entity.User
|
||||
mutex sync.RWMutex
|
||||
}{
|
||||
sid: make(map[string]string),
|
||||
rid: make(map[string]string),
|
||||
user: make(map[string]entity.User),
|
||||
}
|
||||
|
||||
// wsConnection upgrades the HTTP server connection to the WebSocket protocol.
|
||||
var wsConnection = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// wsClient represents information about the WebSocket client.
|
||||
type wsClient struct {
|
||||
AuthToken string `json:"session"`
|
||||
CssUri string `json:"css"`
|
||||
JsUri string `json:"js"`
|
||||
Version string `json:"version"`
|
||||
}
|
58
internal/api/websocket_create.go
Normal file
58
internal/api/websocket_create.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// WebSocket registers the /ws endpoint for establishing websocket connections.
|
||||
func WebSocket(router *gin.RouterGroup) {
|
||||
if router == nil {
|
||||
return
|
||||
}
|
||||
|
||||
conf := get.Config()
|
||||
|
||||
if conf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
router.GET("/ws", func(c *gin.Context) {
|
||||
w := c.Writer
|
||||
r := c.Request
|
||||
|
||||
ws, err := wsConnection.Upgrade(w, r, nil)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var writeMutex sync.Mutex
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
connId := rnd.UUID()
|
||||
|
||||
// Init connection.
|
||||
wsAuth.mutex.Lock()
|
||||
|
||||
if conf.Public() {
|
||||
wsAuth.user[connId] = entity.Admin
|
||||
} else {
|
||||
wsAuth.user[connId] = entity.UnknownUser
|
||||
}
|
||||
|
||||
wsAuth.mutex.Unlock()
|
||||
|
||||
// Init writer.
|
||||
go wsWriter(ws, &writeMutex, connId)
|
||||
|
||||
// Init reader.
|
||||
wsReader(ws, &writeMutex, connId, conf)
|
||||
})
|
||||
}
|
49
internal/api/websocket_reader.go
Normal file
49
internal/api/websocket_reader.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
// wsReader initializes a WebSocket reader for receiving messages.
|
||||
func wsReader(ws *websocket.Conn, writeMutex *sync.Mutex, connId string, conf *config.Config) {
|
||||
defer ws.Close()
|
||||
|
||||
ws.SetReadLimit(4096)
|
||||
|
||||
if err := ws.SetReadDeadline(time.Now().Add(wsTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ws.SetPongHandler(func(string) error { _ = ws.SetReadDeadline(time.Now().Add(wsTimeout)); return nil })
|
||||
|
||||
for {
|
||||
_, m, readErr := ws.ReadMessage()
|
||||
|
||||
if readErr != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var info wsClient
|
||||
|
||||
if jsonErr := json.Unmarshal(m, &info); jsonErr != nil {
|
||||
// Do nothing.
|
||||
} else {
|
||||
if s := Session(info.AuthToken); s != nil {
|
||||
wsAuth.mutex.Lock()
|
||||
wsAuth.sid[connId] = s.ID
|
||||
wsAuth.rid[connId] = s.RefID
|
||||
wsAuth.user[connId] = *s.User()
|
||||
wsAuth.mutex.Unlock()
|
||||
|
||||
wsSendMessage("config.updated", event.Data{"config": conf.ClientSession(s)}, ws, writeMutex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,14 +8,14 @@ import (
|
|||
)
|
||||
|
||||
func TestWebsocket(t *testing.T) {
|
||||
t.Run("bad request", func(t *testing.T) {
|
||||
t.Run("BadRequest", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
WebSocket(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/ws")
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
|
||||
t.Run("router nil", func(t *testing.T) {
|
||||
t.Run("NoRouter", func(t *testing.T) {
|
||||
app, _, _ := NewApiTest()
|
||||
WebSocket(nil)
|
||||
r := PerformRequest(app, "GET", "/api/v1/ws")
|
|
@ -1,8 +1,6 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -11,128 +9,24 @@ import (
|
|||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// wsTimeout specifies the timeout duration for WebSocket connections.
|
||||
var wsTimeout = 90 * time.Second
|
||||
|
||||
// wsSubPerm specifies the permissions required to subscribe to a channel.
|
||||
var wsSubscribePerms = acl.Permissions{acl.ActionSubscribe}
|
||||
|
||||
// wsAuth maps connection IDs to specific users and session IDs.
|
||||
var wsAuth = struct {
|
||||
sid map[string]string
|
||||
rid map[string]string
|
||||
user map[string]entity.User
|
||||
mutex sync.RWMutex
|
||||
}{
|
||||
sid: make(map[string]string),
|
||||
rid: make(map[string]string),
|
||||
user: make(map[string]entity.User),
|
||||
}
|
||||
|
||||
// wsConnection upgrades the HTTP server connection to the WebSocket protocol.
|
||||
var wsConnection = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// wsClient represents information about the WebSocket client.
|
||||
type wsClient struct {
|
||||
AuthToken string `json:"session"`
|
||||
CssUri string `json:"css"`
|
||||
JsUri string `json:"js"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// WebSocket registers the /ws endpoint for establishing websocket connections.
|
||||
func WebSocket(router *gin.RouterGroup) {
|
||||
if router == nil {
|
||||
// wsSendMessage sends a message to the WebSocket client.
|
||||
func wsSendMessage(topic string, data interface{}, ws *websocket.Conn, writeMutex *sync.Mutex) {
|
||||
if topic == "" || ws == nil || writeMutex == nil {
|
||||
return
|
||||
}
|
||||
|
||||
conf := get.Config()
|
||||
writeMutex.Lock()
|
||||
defer writeMutex.Unlock()
|
||||
|
||||
if conf == nil {
|
||||
if err := ws.SetWriteDeadline(time.Now().Add(30 * time.Second)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
router.GET("/ws", func(c *gin.Context) {
|
||||
w := c.Writer
|
||||
r := c.Request
|
||||
|
||||
ws, err := wsConnection.Upgrade(w, r, nil)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var writeMutex sync.Mutex
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
connId := rnd.UUID()
|
||||
|
||||
// Init connection.
|
||||
wsAuth.mutex.Lock()
|
||||
|
||||
if conf.Public() {
|
||||
wsAuth.user[connId] = entity.Admin
|
||||
} else {
|
||||
wsAuth.user[connId] = entity.UnknownUser
|
||||
}
|
||||
|
||||
wsAuth.mutex.Unlock()
|
||||
|
||||
// Init writer.
|
||||
go wsWriter(ws, &writeMutex, connId)
|
||||
|
||||
// Init reader.
|
||||
wsReader(ws, &writeMutex, connId, conf)
|
||||
})
|
||||
}
|
||||
|
||||
// wsReader initializes a WebSocket reader for receiving messages.
|
||||
func wsReader(ws *websocket.Conn, writeMutex *sync.Mutex, connId string, conf *config.Config) {
|
||||
defer ws.Close()
|
||||
|
||||
ws.SetReadLimit(4096)
|
||||
|
||||
if err := ws.SetReadDeadline(time.Now().Add(wsTimeout)); err != nil {
|
||||
} else if err := ws.WriteJSON(gin.H{"event": topic, "data": data}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ws.SetPongHandler(func(string) error { _ = ws.SetReadDeadline(time.Now().Add(wsTimeout)); return nil })
|
||||
|
||||
for {
|
||||
_, m, readErr := ws.ReadMessage()
|
||||
|
||||
if readErr != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var info wsClient
|
||||
|
||||
if jsonErr := json.Unmarshal(m, &info); jsonErr != nil {
|
||||
// Do nothing.
|
||||
} else {
|
||||
if s := Session(info.AuthToken); s != nil {
|
||||
wsAuth.mutex.Lock()
|
||||
wsAuth.sid[connId] = s.ID
|
||||
wsAuth.rid[connId] = s.RefID
|
||||
wsAuth.user[connId] = *s.User()
|
||||
wsAuth.mutex.Unlock()
|
||||
|
||||
wsSendMessage("config.updated", event.Data{"config": conf.ClientSession(s)}, ws, writeMutex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsWriter initializes a WebSocket writer for sending messages.
|
||||
|
@ -228,19 +122,3 @@ func wsWriter(ws *websocket.Conn, writeMutex *sync.Mutex, connId string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsSendMessage sends a message to the WebSocket client.
|
||||
func wsSendMessage(topic string, data interface{}, ws *websocket.Conn, writeMutex *sync.Mutex) {
|
||||
if topic == "" || ws == nil || writeMutex == nil {
|
||||
return
|
||||
}
|
||||
|
||||
writeMutex.Lock()
|
||||
defer writeMutex.Unlock()
|
||||
|
||||
if err := ws.SetWriteDeadline(time.Now().Add(30 * time.Second)); err != nil {
|
||||
return
|
||||
} else if err := ws.WriteJSON(gin.H{"event": topic, "data": data}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/internal/ttl"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/face"
|
||||
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/internal/ttl"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
|
|
@ -12,9 +12,9 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
"github.com/photoprism/photoprism/pkg/list"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
// Security adds common HTTP security headers to the response.
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package session
|
||||
|
||||
// Header specifies the name of the session HTTP header.
|
||||
var Header = "X-Session-ID"
|
|
@ -2,6 +2,7 @@ package header
|
|||
|
||||
const (
|
||||
SessionID = "X-Session-ID"
|
||||
AuthToken = "X-Auth-Token"
|
||||
Authorization = "Authorization" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
|
||||
BasicAuth = "Basic"
|
||||
BearerAuth = "Bearer"
|
Loading…
Reference in New Issue
Block a user