temp
This commit is contained in:
@@ -440,6 +440,8 @@ func main() {
|
||||
privateAPI.GET("/files/preview/:fileID", fileHandler.GetThumbnail)
|
||||
privateAPI.GET("/files/preview/v2/:fileID", fileHandler.GetThumbnail)
|
||||
|
||||
privateAPI.POST("files/share-url", fileHandler.ShareUrl)
|
||||
|
||||
privateAPI.PUT("/files/data", fileHandler.PutFileData)
|
||||
privateAPI.PUT("/files/video-data", fileHandler.PutVideoData)
|
||||
privateAPI.POST("/files/data/status-diff", fileHandler.FileDataStatusDiff)
|
||||
|
||||
@@ -97,8 +97,8 @@ var ErrUserDeleted = errors.New("user account has been deleted")
|
||||
// ErrLockUnavailable is thrown when a lock could not be acquired
|
||||
var ErrLockUnavailable = errors.New("could not acquire lock")
|
||||
|
||||
// ErrActiveLinkAlreadyExists is thrown when the collection already has active public link
|
||||
var ErrActiveLinkAlreadyExists = errors.New("Collection already has active public link")
|
||||
// ErrActiveLinkAlreadyExists is thrown when an active link already exists for entity
|
||||
var ErrActiveLinkAlreadyExists = errors.New("link already exists for this entity")
|
||||
|
||||
// ErrNotImplemented indicates that the action that we tried to perform is not
|
||||
// available at this museum instance. e.g. this could be something that is not
|
||||
|
||||
@@ -5,7 +5,7 @@ type CreateFileUrl struct {
|
||||
FileID int64 `json:"fileID" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateFileResponse represents a response to the UpdateFileRequest
|
||||
// UpdateFileUrl ..
|
||||
type UpdateFileUrl struct {
|
||||
LinkID string `json:"linkID" binding:"required"`
|
||||
FileID int64 `json:"fileID" binding:"required"`
|
||||
@@ -34,6 +34,7 @@ type PublicFileUrlRow struct {
|
||||
PasswordInfo *PassWordInfo
|
||||
EnableDownload bool
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type FileUrl struct {
|
||||
|
||||
28
server/pkg/api/file_url.go
Normal file
28
server/pkg/api/file_url.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/museum/pkg/utils/handler"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Update updates already existing file
|
||||
func (h *FileHandler) ShareUrl(c *gin.Context) {
|
||||
enteApp := auth.GetApp(c)
|
||||
userID := auth.GetUserID(c.Request.Header)
|
||||
var file ente.CreateFileUrl
|
||||
if err := c.ShouldBindJSON(&file); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.Controller.Update(c, userID, file, enteApp)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller/access"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
@@ -55,20 +56,28 @@ type PublicCollectionController struct {
|
||||
FileController *FileController
|
||||
EmailNotificationCtrl *emailCtrl.EmailNotificationController
|
||||
PublicCollectionRepo *public.PublicCollectionRepository
|
||||
PublicFileRepo *public.PublicFileRepository
|
||||
CollectionRepo *repo.CollectionRepository
|
||||
UserRepo *repo.UserRepository
|
||||
JwtSecret []byte
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) CreateAccessToken(ctx context.Context, req ente.CreatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
func (c *PublicCollectionController) CreateFileUrl(ctx *gin.Context, req ente.CreateFileUrl) (*ente.FileUrl, error) {
|
||||
|
||||
userID := auth.GetUserID(ctx.Request.Header)
|
||||
if err := c.FileController.AccessCtrl.VerifyFileOwnership(ctx, &access.VerifyFileOwnershipParams{
|
||||
ActorUserId: userID,
|
||||
FileIDs: []int64{req.FileID},
|
||||
}); err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to verify file ownership")
|
||||
}
|
||||
accessToken := shortuuid.New()[0:AccessTokenLength]
|
||||
err := c.PublicCollectionRepo.
|
||||
Insert(ctx, req.CollectionID, accessToken, req.ValidTill, req.DeviceLimit, req.EnableCollect, req.EnableJoin)
|
||||
err := c.PublicFileRepo.Insert(ctx, req.FileID, userID, accessToken)
|
||||
if err != nil {
|
||||
if errors.Is(err, ente.ErrActiveLinkAlreadyExists) {
|
||||
collectionToPubUrlMap, err2 := c.PublicCollectionRepo.GetCollectionToActivePublicURLMap(ctx, []int64{req.CollectionID})
|
||||
if err2 != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err2, "")
|
||||
return nil, stacktrace.Propagate(err2, "")
|
||||
}
|
||||
if publicUrls, ok := collectionToPubUrlMap[req.CollectionID]; ok {
|
||||
if len(publicUrls) > 0 {
|
||||
|
||||
236
server/pkg/controller/public_file.go
Normal file
236
server/pkg/controller/public_file.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/ente"
|
||||
enteJWT "github.com/ente-io/museum/ente/jwt"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (c *PublicCollectionController) CreateAccessToken(ctx context.Context, req ente.CreatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
accessToken := shortuuid.New()[0:AccessTokenLength]
|
||||
err := c.PublicCollectionRepo.
|
||||
Insert(ctx, req.CollectionID, accessToken, req.ValidTill, req.DeviceLimit, req.EnableCollect, req.EnableJoin)
|
||||
if err != nil {
|
||||
if errors.Is(err, ente.ErrActiveLinkAlreadyExists) {
|
||||
collectionToPubUrlMap, err2 := c.PublicCollectionRepo.GetCollectionToActivePublicURLMap(ctx, []int64{req.CollectionID})
|
||||
if err2 != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err2, "")
|
||||
}
|
||||
if publicUrls, ok := collectionToPubUrlMap[req.CollectionID]; ok {
|
||||
if len(publicUrls) > 0 {
|
||||
return publicUrls[0], nil
|
||||
}
|
||||
}
|
||||
// ideally we should never reach here
|
||||
return ente.PublicURL{}, stacktrace.NewError("Unexpected state")
|
||||
} else {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
}
|
||||
response := ente.PublicURL{
|
||||
URL: c.PublicCollectionRepo.GetAlbumUrl(accessToken),
|
||||
ValidTill: req.ValidTill,
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
EnableDownload: true,
|
||||
EnableCollect: req.EnableCollect,
|
||||
PasswordEnabled: false,
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) GetActivePublicCollectionToken(ctx context.Context, collectionID int64) (ente.PublicCollectionToken, error) {
|
||||
return c.PublicCollectionRepo.GetActivePublicCollectionToken(ctx, collectionID)
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) CreateFile(ctx *gin.Context, file ente.File, app ente.App) (ente.File, error) {
|
||||
collection, err := c.GetPublicCollection(ctx, true)
|
||||
if err != nil {
|
||||
return ente.File{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
collectionOwnerID := collection.Owner.ID
|
||||
// Do not let any update happen via public Url
|
||||
file.ID = 0
|
||||
file.OwnerID = collectionOwnerID
|
||||
file.UpdationTime = time.Microseconds()
|
||||
file.IsDeleted = false
|
||||
createdFile, err := c.FileController.Create(ctx, collectionOwnerID, file, ctx.Request.UserAgent(), app)
|
||||
if err != nil {
|
||||
return ente.File{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
// Note: Stop sending email notification for public collection till
|
||||
// we add in-app setting to enable/disable email notifications
|
||||
//go c.EmailNotificationCtrl.OnFilesCollected(file.OwnerID)
|
||||
return createdFile, nil
|
||||
}
|
||||
|
||||
// Disable all public accessTokens generated for the given cID till date.
|
||||
func (c *PublicCollectionController) Disable(ctx context.Context, cID int64) error {
|
||||
err := c.PublicCollectionRepo.DisableSharing(ctx, cID)
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) UpdateSharedUrl(ctx context.Context, req ente.UpdatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
publicCollectionToken, err := c.PublicCollectionRepo.GetActivePublicCollectionToken(ctx, req.CollectionID)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, err
|
||||
}
|
||||
if req.ValidTill != nil {
|
||||
publicCollectionToken.ValidTill = *req.ValidTill
|
||||
}
|
||||
if req.DeviceLimit != nil {
|
||||
publicCollectionToken.DeviceLimit = *req.DeviceLimit
|
||||
}
|
||||
if req.PassHash != nil && req.Nonce != nil && req.OpsLimit != nil && req.MemLimit != nil {
|
||||
publicCollectionToken.PassHash = req.PassHash
|
||||
publicCollectionToken.Nonce = req.Nonce
|
||||
publicCollectionToken.OpsLimit = req.OpsLimit
|
||||
publicCollectionToken.MemLimit = req.MemLimit
|
||||
} else if req.DisablePassword != nil && *req.DisablePassword {
|
||||
publicCollectionToken.PassHash = nil
|
||||
publicCollectionToken.Nonce = nil
|
||||
publicCollectionToken.OpsLimit = nil
|
||||
publicCollectionToken.MemLimit = nil
|
||||
}
|
||||
if req.EnableDownload != nil {
|
||||
publicCollectionToken.EnableDownload = *req.EnableDownload
|
||||
}
|
||||
if req.EnableCollect != nil {
|
||||
publicCollectionToken.EnableCollect = *req.EnableCollect
|
||||
}
|
||||
if req.EnableJoin != nil {
|
||||
publicCollectionToken.EnableJoin = *req.EnableJoin
|
||||
}
|
||||
err = c.PublicCollectionRepo.UpdatePublicCollectionToken(ctx, publicCollectionToken)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return ente.PublicURL{
|
||||
URL: c.PublicCollectionRepo.GetAlbumUrl(publicCollectionToken.Token),
|
||||
DeviceLimit: publicCollectionToken.DeviceLimit,
|
||||
ValidTill: publicCollectionToken.ValidTill,
|
||||
EnableDownload: publicCollectionToken.EnableDownload,
|
||||
EnableCollect: publicCollectionToken.EnableCollect,
|
||||
EnableJoin: publicCollectionToken.EnableJoin,
|
||||
PasswordEnabled: publicCollectionToken.PassHash != nil && *publicCollectionToken.PassHash != "",
|
||||
Nonce: publicCollectionToken.Nonce,
|
||||
MemLimit: publicCollectionToken.MemLimit,
|
||||
OpsLimit: publicCollectionToken.OpsLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyPassword verifies if the user has provided correct pw hash. If yes, it returns a signed jwt token which can be
|
||||
// used by the client to pass in other requests for public collection.
|
||||
// Having a separate endpoint for password validation allows us to easily rate-limit the attempts for brute-force
|
||||
// attack for guessing password.
|
||||
func (c *PublicCollectionController) VerifyPassword(ctx *gin.Context, req ente.VerifyPasswordRequest) (*ente.VerifyPasswordResponse, error) {
|
||||
accessContext := auth.MustGetPublicAccessContext(ctx)
|
||||
publicCollectionToken, err := c.PublicCollectionRepo.GetActivePublicCollectionToken(ctx, accessContext.CollectionID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get public collection info")
|
||||
}
|
||||
if publicCollectionToken.PassHash == nil || *publicCollectionToken.PassHash == "" {
|
||||
return nil, stacktrace.Propagate(ente.ErrBadRequest, "password is not configured for the link")
|
||||
}
|
||||
if req.PassHash != *publicCollectionToken.PassHash {
|
||||
return nil, stacktrace.Propagate(ente.ErrInvalidPassword, "incorrect password for link")
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &enteJWT.PublicAlbumPasswordClaim{
|
||||
PassHash: req.PassHash,
|
||||
ExpiryTime: time.NDaysFromNow(365),
|
||||
})
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(c.JwtSecret)
|
||||
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return &ente.VerifyPasswordResponse{
|
||||
JWTToken: tokenString,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) ValidateJWTToken(ctx *gin.Context, jwtToken string, passwordHash string) error {
|
||||
token, err := jwt.ParseWithClaims(jwtToken, &enteJWT.PublicAlbumPasswordClaim{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return stacktrace.Propagate(fmt.Errorf("unexpected signing method: %v", token.Header["alg"]), ""), nil
|
||||
}
|
||||
return c.JwtSecret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "JWT parsed failed")
|
||||
}
|
||||
claims, ok := token.Claims.(*enteJWT.PublicAlbumPasswordClaim)
|
||||
|
||||
if !ok {
|
||||
return stacktrace.Propagate(errors.New("no claim in jwt token"), "")
|
||||
}
|
||||
if token.Valid && claims.PassHash == passwordHash {
|
||||
return nil
|
||||
}
|
||||
return ente.ErrInvalidPassword
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) HandleAccountDeletion(ctx context.Context, userID int64, logger *logrus.Entry) error {
|
||||
logger.Info("updating public collection on account deletion")
|
||||
collectionIDs, err := c.PublicCollectionRepo.GetActivePublicTokenForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
logger.WithField("cIDs", collectionIDs).Info("disable public tokens due to account deletion")
|
||||
for _, collectionID := range collectionIDs {
|
||||
err = c.Disable(ctx, collectionID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPublicCollection will return collection info for a public url.
|
||||
// is mustAllowCollect is set to true but the underlying collection doesn't allow uploading
|
||||
func (c *PublicCollectionController) GetPublicCollection(ctx *gin.Context, mustAllowCollect bool) (ente.Collection, error) {
|
||||
accessContext := auth.MustGetPublicAccessContext(ctx)
|
||||
collection, err := c.CollectionRepo.Get(accessContext.CollectionID)
|
||||
if err != nil {
|
||||
return ente.Collection{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
if collection.IsDeleted {
|
||||
return ente.Collection{}, stacktrace.Propagate(ente.ErrNotFound, "collection is deleted")
|
||||
}
|
||||
// hide redundant/private information
|
||||
collection.Sharees = nil
|
||||
collection.MagicMetadata = nil
|
||||
publicURLsWithLimitedInfo := make([]ente.PublicURL, 0)
|
||||
for _, publicUrl := range collection.PublicURLs {
|
||||
publicURLsWithLimitedInfo = append(publicURLsWithLimitedInfo, ente.PublicURL{
|
||||
EnableDownload: publicUrl.EnableDownload,
|
||||
EnableCollect: publicUrl.EnableCollect,
|
||||
PasswordEnabled: publicUrl.PasswordEnabled,
|
||||
Nonce: publicUrl.Nonce,
|
||||
MemLimit: publicUrl.MemLimit,
|
||||
OpsLimit: publicUrl.OpsLimit,
|
||||
EnableJoin: publicUrl.EnableJoin,
|
||||
})
|
||||
}
|
||||
collection.PublicURLs = publicURLsWithLimitedInfo
|
||||
if mustAllowCollect {
|
||||
if len(publicURLsWithLimitedInfo) != 1 {
|
||||
errorMsg := fmt.Sprintf("Unexpected number of public urls: %d", len(publicURLsWithLimitedInfo))
|
||||
return ente.Collection{}, stacktrace.Propagate(ente.NewInternalError(errorMsg), "")
|
||||
}
|
||||
if !publicURLsWithLimitedInfo[0].EnableCollect {
|
||||
return ente.Collection{}, stacktrace.Propagate(&ente.ErrPublicCollectDisabled, "")
|
||||
}
|
||||
}
|
||||
return collection, nil
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (pcr *PublicCollectionRepository) RecordAbuseReport(ctx context.Context, ac
|
||||
url string, reason string, details ente.AbuseReportDetails) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `INSERT INTO public_abuse_report
|
||||
(share_id, ip, user_agent, url, reason, details) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT ON CONSTRAINT unique_report_sid_ip_ua DO UPDATE SET (reason, details) = ($5, $6)`,
|
||||
ON CONFLICT ON CONSTRAINT unique_report_public_collection_id_ip_ua DO UPDATE SET (reason, details) = ($5, $6)`,
|
||||
accessCtx.ID, accessCtx.IP, accessCtx.UserAgent, url, reason, details)
|
||||
return stacktrace.Propagate(err, "failed to record abuse report")
|
||||
}
|
||||
|
||||
161
server/pkg/repo/public/public_file.go
Normal file
161
server/pkg/repo/public/public_file.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/ente/base"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// PublicFileRepository defines the methods for inserting, updating and
|
||||
// retrieving entities related to public file
|
||||
type PublicFileRepository struct {
|
||||
DB *sql.DB
|
||||
albumHost string
|
||||
}
|
||||
|
||||
// NewPublicFileRepository ..
|
||||
func NewPublicFileRepository(db *sql.DB, albumHost string) *PublicFileRepository {
|
||||
if albumHost == "" {
|
||||
albumHost = "https://albums.ente.io"
|
||||
}
|
||||
return &PublicFileRepository{
|
||||
DB: db,
|
||||
albumHost: albumHost,
|
||||
}
|
||||
}
|
||||
|
||||
func (pcr *PublicFileRepository) GetAlbumUrl(token string) string {
|
||||
return fmt.Sprintf("%s/?t=%s", pcr.albumHost, token)
|
||||
}
|
||||
|
||||
func (pcr *PublicFileRepository) Insert(
|
||||
ctx context.Context,
|
||||
fileID int64,
|
||||
ownerID int64,
|
||||
token string,
|
||||
) error {
|
||||
id, err := base.NewID("pft")
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to generate new ID for public file token")
|
||||
}
|
||||
_, err = pcr.DB.ExecContext(ctx, `INSERT INTO public_file_tokens
|
||||
(id, file_id, owner_id, access_token) VALUES ($1, $2, $3, $4)`,
|
||||
id, fileID, ownerID, token)
|
||||
if err != nil && err.Error() == "pq: duplicate key value violates unique constraint \"public_access_token_unique_idx\"" {
|
||||
return ente.ErrActiveLinkAlreadyExists
|
||||
}
|
||||
return stacktrace.Propagate(err, "failed to insert")
|
||||
}
|
||||
|
||||
func (pcr *PublicFileRepository) DisableSharing(ctx context.Context, fileID int64) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `UPDATE public_file_tokens SET is_disabled = true where
|
||||
file_id = $1 and is_disabled = false`, fileID)
|
||||
return stacktrace.Propagate(err, "failed to disable sharing")
|
||||
}
|
||||
|
||||
// GetCollectionToActivePublicURLMap will return map of collectionID to PublicURLs which are not disabled yet.
|
||||
// Note: The url could be expired or deviceLimit is already reached
|
||||
func (pcr *PublicFileRepository) GetCollectionToActivePublicURLMap(ctx context.Context, collectionIDs []int64) (map[int64][]ente.PublicURL, error) {
|
||||
rows, err := pcr.DB.QueryContext(ctx, `SELECT collection_id, access_token, valid_till, device_limit, enable_download, enable_collect, enable_join, pw_nonce, mem_limit, ops_limit FROM
|
||||
public_collection_tokens WHERE collection_id = ANY($1) and is_disabled = FALSE`,
|
||||
pq.Array(collectionIDs))
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
defer rows.Close()
|
||||
result := make(map[int64][]ente.PublicURL, 0)
|
||||
for _, cID := range collectionIDs {
|
||||
result[cID] = make([]ente.PublicURL, 0)
|
||||
}
|
||||
for rows.Next() {
|
||||
publicUrl := ente.PublicURL{}
|
||||
var collectionID int64
|
||||
var accessToken string
|
||||
var nonce *string
|
||||
var opsLimit, memLimit *int64
|
||||
if err = rows.Scan(&collectionID, &accessToken, &publicUrl.ValidTill, &publicUrl.DeviceLimit, &publicUrl.EnableDownload, &publicUrl.EnableCollect, &publicUrl.EnableJoin, &nonce, &memLimit, &opsLimit); err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
publicUrl.URL = pcr.GetAlbumUrl(accessToken)
|
||||
if nonce != nil {
|
||||
publicUrl.Nonce = nonce
|
||||
publicUrl.MemLimit = memLimit
|
||||
publicUrl.OpsLimit = opsLimit
|
||||
publicUrl.PasswordEnabled = true
|
||||
}
|
||||
result[collectionID] = append(result[collectionID], publicUrl)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetActivePublicCollectionToken will return ente.PublicCollectionToken for given collection ID
|
||||
// Note: The token could be expired or deviceLimit is already reached
|
||||
func (pcr *PublicFileRepository) GetActivePublicCollectionToken(ctx context.Context, collectionID int64) (ente.PublicCollectionToken, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `SELECT id, collection_id, access_token, valid_till, device_limit,
|
||||
is_disabled, pw_hash, pw_nonce, mem_limit, ops_limit, enable_download, enable_collect, enable_join FROM
|
||||
public_collection_tokens WHERE collection_id = $1 and is_disabled = FALSE`,
|
||||
collectionID)
|
||||
|
||||
//defer rows.Close()
|
||||
ret := ente.PublicCollectionToken{}
|
||||
err := row.Scan(&ret.ID, &ret.CollectionID, &ret.Token, &ret.ValidTill, &ret.DeviceLimit,
|
||||
&ret.IsDisabled, &ret.PassHash, &ret.Nonce, &ret.MemLimit, &ret.OpsLimit, &ret.EnableDownload, &ret.EnableCollect, &ret.EnableJoin)
|
||||
if err != nil {
|
||||
return ente.PublicCollectionToken{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// UpdatePublicCollectionToken will update the row for corresponding public collection token
|
||||
func (pcr *PublicFileRepository) UpdatePublicCollectionToken(ctx context.Context, pct ente.PublicCollectionToken) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `UPDATE public_collection_tokens SET valid_till = $1, device_limit = $2,
|
||||
pw_hash = $3, pw_nonce = $4, mem_limit = $5, ops_limit = $6, enable_download = $7, enable_collect = $8, enable_join = $9
|
||||
where id = $10`,
|
||||
pct.ValidTill, pct.DeviceLimit, pct.PassHash, pct.Nonce, pct.MemLimit, pct.OpsLimit, pct.EnableDownload, pct.EnableCollect, pct.EnableJoin, pct.ID)
|
||||
return stacktrace.Propagate(err, "failed to update public collection token")
|
||||
}
|
||||
|
||||
func (pcr *PublicFileRepository) GetUniqueAccessCount(ctx context.Context, shareId int64) (int64, error) {
|
||||
panic("not implemented, refactor & public collection")
|
||||
}
|
||||
|
||||
func (pcr *PublicFileRepository) RecordAccessHistory(ctx context.Context, shareID int64, ip string, ua string) error {
|
||||
panic("not implemented, refactor & public collection")
|
||||
}
|
||||
|
||||
// AccessedInPast returns true if the given ip, ua agent combination has accessed the url in the past
|
||||
func (pcr *PublicFileRepository) AccessedInPast(ctx context.Context, shareID int64, ip string, ua string) (bool, error) {
|
||||
panic("not implemented, refactor & public collection")
|
||||
}
|
||||
|
||||
func (pcr *PublicFileRepository) GetFileUrlRowByToken(ctx context.Context, accessToken string) (*ente.PublicFileUrlRow, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx,
|
||||
`SELECT id, file_id, is_disabled, valid_till, device_limit, password_info,
|
||||
created_at, updated_at
|
||||
from public_file_tokens
|
||||
where access_token = $1
|
||||
`, accessToken)
|
||||
var result = ente.PublicFileUrlRow{}
|
||||
err := row.Scan(&result.LinkID, &result.FileID, &result.IsDisabled, &result.ValidTill, &result.DeviceLimit, result.PasswordInfo, &result.CreatedAt, &result.UpdatedAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ente.ErrNotFound
|
||||
}
|
||||
return nil, stacktrace.Propagate(err, "failed to get public file url summary by token")
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// CleanupAccessHistory public_collection_access_history where public_collection_tokens is disabled and the last updated time is older than 30 days
|
||||
func (pcr *PublicFileRepository) CleanupAccessHistory(ctx context.Context) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `DELETE FROM public_collection_access_history WHERE share_id IN (SELECT id FROM public_collection_tokens WHERE is_disabled = TRUE AND updated_at < (now_utc_micro_seconds() - (24::BIGINT * 30 * 60 * 60 * 1000 * 1000)))`)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to clean up public collection access history")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user