[add] admin panel, [add] better logging
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
26
api/api.go
26
api/api.go
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/multitemplate"
|
||||
"github.com/gin-contrib/sessions"
|
||||
@@ -32,12 +33,15 @@ type API struct {
|
||||
func NewApi(db *database.DBManager, c *config.Config, assets *embed.FS) *API {
|
||||
api := &API{
|
||||
HTMLPolicy: bluemonday.StrictPolicy(),
|
||||
Router: gin.Default(),
|
||||
Router: gin.New(),
|
||||
Config: c,
|
||||
DB: db,
|
||||
Assets: assets,
|
||||
}
|
||||
|
||||
// Add Logger
|
||||
api.Router.Use(apiLogger())
|
||||
|
||||
// Assets & Web App Templates
|
||||
assetsDir, _ := fs.Sub(assets, "assets")
|
||||
api.Router.StaticFS("/assets", http.FS(assetsDir))
|
||||
@@ -107,6 +111,9 @@ func (api *API) registerWebAppRoutes() {
|
||||
api.Router.GET("/logout", api.authWebAppMiddleware, api.appAuthLogout)
|
||||
api.Router.GET("/register", api.appGetRegister)
|
||||
api.Router.GET("/settings", api.authWebAppMiddleware, api.appGetSettings)
|
||||
api.Router.GET("/admin/logs", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdminLogs)
|
||||
api.Router.GET("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdmin)
|
||||
api.Router.POST("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appPerformAdminAction)
|
||||
api.Router.POST("/login", api.appAuthFormLogin)
|
||||
api.Router.POST("/register", api.appAuthFormRegister)
|
||||
|
||||
@@ -228,6 +235,23 @@ func (api *API) generateTemplates() *multitemplate.Renderer {
|
||||
return &render
|
||||
}
|
||||
|
||||
func apiLogger() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Start Timer
|
||||
startTime := time.Now()
|
||||
|
||||
// Process Request
|
||||
c.Next()
|
||||
|
||||
// End Timer
|
||||
endTime := time.Now()
|
||||
latency := endTime.Sub(startTime).Round(time.Microsecond)
|
||||
|
||||
// Log Result
|
||||
log.Infof("[HTTPRouter] %-15s (%10s) %d %7s %s", c.ClientIP(), latency, c.Writer.Status(), c.Request.Method, c.Request.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func generateToken(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -24,6 +27,29 @@ import (
|
||||
"reichard.io/bbank/utils"
|
||||
)
|
||||
|
||||
type AdminAction string
|
||||
|
||||
const (
|
||||
AA_IMPORT AdminAction = "IMPORT"
|
||||
AA_BACKUP AdminAction = "BACKUP"
|
||||
AA_RESTORE AdminAction = "RESTORE"
|
||||
AA_METADATA_MATCH AdminAction = "METADATA_MATCH"
|
||||
)
|
||||
|
||||
type ImportType string
|
||||
|
||||
const (
|
||||
IMPORT_TYPE_DIRECT ImportType = "DIRECT"
|
||||
IMPORT_TYPE_COPY ImportType = "COPY"
|
||||
)
|
||||
|
||||
type BackupType string
|
||||
|
||||
const (
|
||||
BACKUP_TYPE_COVERS BackupType = "COVERS"
|
||||
BACKUP_TYPE_DOCUMENTS BackupType = "DOCUMENTS"
|
||||
)
|
||||
|
||||
type queryParams struct {
|
||||
Page *int64 `form:"page"`
|
||||
Limit *int64 `form:"limit"`
|
||||
@@ -51,6 +77,20 @@ type requestDocumentEdit struct {
|
||||
CoverFile *multipart.FileHeader `form:"cover_file"`
|
||||
}
|
||||
|
||||
type requestAdminAction struct {
|
||||
Action AdminAction `form:"action"`
|
||||
|
||||
// Import Action
|
||||
ImportDirectory *string `form:"import_directory"`
|
||||
ImportType *ImportType `form:"import_type"`
|
||||
|
||||
// Backup Action
|
||||
BackupTypes []BackupType `form:"backup_types"`
|
||||
|
||||
// Restore Action
|
||||
RestoreFile *multipart.FileHeader `form:"restore_file"`
|
||||
}
|
||||
|
||||
type requestDocumentIdentify struct {
|
||||
Title *string `form:"title"`
|
||||
Author *string `form:"author"`
|
||||
@@ -93,7 +133,7 @@ func (api *API) appDocumentReader(c *gin.Context) {
|
||||
|
||||
func (api *API) appGetDocuments(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("documents", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
qParams := bindQueryParams(c, 9)
|
||||
|
||||
var query *string
|
||||
@@ -103,7 +143,7 @@ func (api *API) appGetDocuments(c *gin.Context) {
|
||||
}
|
||||
|
||||
documents, err := api.DB.Queries.GetDocumentsWithStats(api.DB.Ctx, database.GetDocumentsWithStatsParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
Query: query,
|
||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||
Limit: *qParams.Limit,
|
||||
@@ -145,7 +185,7 @@ func (api *API) appGetDocuments(c *gin.Context) {
|
||||
|
||||
func (api *API) appGetDocument(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("document", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
@@ -155,7 +195,7 @@ func (api *API) appGetDocument(c *gin.Context) {
|
||||
}
|
||||
|
||||
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
DocumentID: rDocID.DocumentID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -172,11 +212,12 @@ func (api *API) appGetDocument(c *gin.Context) {
|
||||
|
||||
func (api *API) appGetProgress(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("progress", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
|
||||
qParams := bindQueryParams(c, 15)
|
||||
|
||||
progressFilter := database.GetProgressParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||
Limit: *qParams.Limit,
|
||||
}
|
||||
@@ -200,11 +241,11 @@ func (api *API) appGetProgress(c *gin.Context) {
|
||||
|
||||
func (api *API) appGetActivity(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("activity", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
qParams := bindQueryParams(c, 15)
|
||||
|
||||
activityFilter := database.GetActivityParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||
Limit: *qParams.Limit,
|
||||
}
|
||||
@@ -228,17 +269,17 @@ func (api *API) appGetActivity(c *gin.Context) {
|
||||
|
||||
func (api *API) appGetHome(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("home", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
|
||||
start := time.Now()
|
||||
graphData, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, userID)
|
||||
log.Info("GetDailyReadStats Performance: ", time.Since(start))
|
||||
graphData, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, auth.UserName)
|
||||
log.Debug("[appGetHome] GetDailyReadStats Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
databaseInfo, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, userID)
|
||||
log.Info("GetDatabaseInfo Performance: ", time.Since(start))
|
||||
databaseInfo, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, auth.UserName)
|
||||
log.Debug("[appGetHome] GetDatabaseInfo Performance: ", time.Since(start))
|
||||
|
||||
streaks, _ := api.DB.Queries.GetUserStreaks(api.DB.Ctx, userID)
|
||||
streaks, _ := api.DB.Queries.GetUserStreaks(api.DB.Ctx, auth.UserName)
|
||||
WPMLeaderboard, _ := api.DB.Queries.GetWPMLeaderboard(api.DB.Ctx)
|
||||
|
||||
templateVars["Data"] = gin.H{
|
||||
@@ -253,16 +294,16 @@ func (api *API) appGetHome(c *gin.Context) {
|
||||
|
||||
func (api *API) appGetSettings(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("settings", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, userID)
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appGetSettings] GetUser DB Error:", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, userID)
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appGetSettings] GetDevices DB Error:", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||
@@ -277,6 +318,126 @@ func (api *API) appGetSettings(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "page/settings", templateVars)
|
||||
}
|
||||
|
||||
func (api *API) appGetAdmin(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("admin", c)
|
||||
|
||||
c.HTML(http.StatusOK, "page/admin", templateVars)
|
||||
}
|
||||
|
||||
func (api *API) appGetAdminLogs(c *gin.Context) {
|
||||
// Open Log File
|
||||
logPath := path.Join(api.Config.ConfigPath, "logs/antholume.log")
|
||||
logFile, err := os.Open(logPath)
|
||||
if err != nil {
|
||||
errorPage(c, http.StatusBadRequest, "Missing AnthoLume log file.")
|
||||
return
|
||||
}
|
||||
defer logFile.Close()
|
||||
|
||||
// Write Log File
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
_, err = io.Copy(w, logFile)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("admin", c)
|
||||
|
||||
var rAdminAction requestAdminAction
|
||||
if err := c.ShouldBind(&rAdminAction); err != nil {
|
||||
log.Error("[appPerformAdminAction] Invalid Form Bind")
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
switch rAdminAction.Action {
|
||||
case AA_IMPORT:
|
||||
// TODO
|
||||
case AA_METADATA_MATCH:
|
||||
// TODO
|
||||
// 1. Documents xref most recent metadata table?
|
||||
// 2. Select all / deselect?
|
||||
case AA_RESTORE:
|
||||
// TODO
|
||||
// 1. Consume backup ZIP
|
||||
// 2. Move existing to "backup" folder (db, wal, shm, covers, documents)
|
||||
// 3. Extract backup zip
|
||||
// 4. Restart server?
|
||||
case AA_BACKUP:
|
||||
// Get File Paths
|
||||
fileName := fmt.Sprintf("%s.db", api.Config.DBName)
|
||||
dbLocation := path.Join(api.Config.ConfigPath, fileName)
|
||||
|
||||
c.Header("Content-type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"AnthoLumeExport_%s.zip\"", time.Now().Format("20060102")))
|
||||
|
||||
// Stream Backup ZIP Archive
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
ar := zip.NewWriter(w)
|
||||
|
||||
exportWalker := func(currentPath string, f fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open File on Disk
|
||||
file, err := os.Open(currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Derive Export Structure
|
||||
fileName := filepath.Base(currentPath)
|
||||
folderName := filepath.Base(filepath.Dir(currentPath))
|
||||
|
||||
// Create File in Export
|
||||
newF, err := ar.Create(path.Join(folderName, fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy File in Export
|
||||
_, err = io.Copy(newF, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy Database File
|
||||
dbFile, _ := os.Open(dbLocation)
|
||||
newDbFile, _ := ar.Create(fileName)
|
||||
io.Copy(newDbFile, dbFile)
|
||||
|
||||
// Backup Covers & Documents
|
||||
for _, item := range rAdminAction.BackupTypes {
|
||||
if item == BACKUP_TYPE_COVERS {
|
||||
filepath.WalkDir(path.Join(api.Config.ConfigPath, "covers"), exportWalker)
|
||||
|
||||
} else if item == BACKUP_TYPE_DOCUMENTS {
|
||||
filepath.WalkDir(path.Join(api.Config.ConfigPath, "documents"), exportWalker)
|
||||
}
|
||||
}
|
||||
|
||||
ar.Close()
|
||||
return false
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "page/admin", templateVars)
|
||||
}
|
||||
|
||||
func (api *API) appGetSearch(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("search", c)
|
||||
|
||||
@@ -320,7 +481,10 @@ func (api *API) appGetRegister(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) appGetDocumentProgress(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
var rDoc requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||
@@ -331,7 +495,7 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
||||
|
||||
progress, err := api.DB.Queries.GetDocumentProgress(api.DB.Ctx, database.GetDocumentProgressParams{
|
||||
DocumentID: rDoc.DocumentID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
})
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
@@ -341,7 +505,7 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
||||
}
|
||||
|
||||
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DocumentID: rDoc.DocumentID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -361,9 +525,12 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) appGetDevices(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, rUser.(string))
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, auth.UserName)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.Error("[appGetDevices] GetDevices DB Error:", err)
|
||||
@@ -677,7 +844,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
|
||||
// Get Template Variables
|
||||
templateVars := api.getBaseTemplateVars("document", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
|
||||
// Get Metadata
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.GBOOK, metadata.MetadataInfo{
|
||||
@@ -710,7 +877,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
}
|
||||
|
||||
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
DocumentID: rDocID.DocumentID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -896,17 +1063,19 @@ func (api *API) appEditSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
templateVars := api.getBaseTemplateVars("settings", c)
|
||||
userID := templateVars["User"].(string)
|
||||
auth := templateVars["Authorization"].(authData)
|
||||
|
||||
newUserSettings := database.UpdateUserParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
}
|
||||
|
||||
// Set New Password
|
||||
if rUserSettings.Password != nil && rUserSettings.NewPassword != nil {
|
||||
password := fmt.Sprintf("%x", md5.Sum([]byte(*rUserSettings.Password)))
|
||||
authorized := api.authorizeCredentials(userID, password)
|
||||
if authorized == true {
|
||||
data := api.authorizeCredentials(auth.UserName, password)
|
||||
if data == nil {
|
||||
templateVars["PasswordErrorMessage"] = "Invalid Password"
|
||||
} else {
|
||||
password := fmt.Sprintf("%x", md5.Sum([]byte(*rUserSettings.NewPassword)))
|
||||
hashedPassword, err := argon2.CreateHash(password, argon2.DefaultParams)
|
||||
if err != nil {
|
||||
@@ -915,8 +1084,6 @@ func (api *API) appEditSettings(c *gin.Context) {
|
||||
templateVars["PasswordMessage"] = "Password Updated"
|
||||
newUserSettings.Password = &hashedPassword
|
||||
}
|
||||
} else {
|
||||
templateVars["PasswordErrorMessage"] = "Invalid Password"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,7 +1102,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Get User
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, userID)
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appEditSettings] GetUser DB Error:", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||
@@ -943,7 +1110,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Get Devices
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, userID)
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appEditSettings] GetDevices DB Error:", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||
@@ -1002,14 +1169,14 @@ func (api *API) getDocumentsWordCount(documents []database.GetDocumentsWithStats
|
||||
}
|
||||
|
||||
func (api *API) getBaseTemplateVars(routeName string, c *gin.Context) gin.H {
|
||||
var userID string
|
||||
if rUser, _ := c.Get("AuthorizedUser"); rUser != nil {
|
||||
userID = rUser.(string)
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"User": userID,
|
||||
"RouteName": routeName,
|
||||
"Authorization": auth,
|
||||
"RouteName": routeName,
|
||||
"Config": gin.H{
|
||||
"Version": api.Config.Version,
|
||||
"SearchEnabled": api.Config.SearchEnabled,
|
||||
|
||||
98
api/auth.go
98
api/auth.go
@@ -14,6 +14,12 @@ import (
|
||||
"reichard.io/bbank/database"
|
||||
)
|
||||
|
||||
// Authorization Data
|
||||
type authData struct {
|
||||
UserName string
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
// KOSync API Auth Headers
|
||||
type authKOHeader struct {
|
||||
AuthUser string `header:"x-auth-user"`
|
||||
@@ -25,25 +31,28 @@ type authOPDSHeader struct {
|
||||
Authorization string `header:"authorization"`
|
||||
}
|
||||
|
||||
func (api *API) authorizeCredentials(username string, password string) (authorized bool) {
|
||||
func (api *API) authorizeCredentials(username string, password string) (auth *authData) {
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, username)
|
||||
if err != nil {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || match != true {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
return true
|
||||
return &authData{
|
||||
UserName: user.ID,
|
||||
IsAdmin: user.Admin,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) authKOMiddleware(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
|
||||
// Check Session First
|
||||
if user, ok := getSession(session); ok == true {
|
||||
c.Set("AuthorizedUser", user)
|
||||
if auth, ok := getSession(session); ok == true {
|
||||
c.Set("Authorization", auth)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
return
|
||||
@@ -61,17 +70,18 @@ func (api *API) authKOMiddleware(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if authorized := api.authorizeCredentials(rHeader.AuthUser, rHeader.AuthKey); authorized != true {
|
||||
authData := api.authorizeCredentials(rHeader.AuthUser, rHeader.AuthKey)
|
||||
if authData == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := setSession(session, rHeader.AuthUser); err != nil {
|
||||
if err := setSession(session, *authData); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("AuthorizedUser", rHeader.AuthUser)
|
||||
c.Set("Authorization", *authData)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
}
|
||||
@@ -89,12 +99,13 @@ func (api *API) authOPDSMiddleware(c *gin.Context) {
|
||||
|
||||
// Validate Auth
|
||||
password := fmt.Sprintf("%x", md5.Sum([]byte(rawPassword)))
|
||||
if authorized := api.authorizeCredentials(user, password); authorized != true {
|
||||
authData := api.authorizeCredentials(user, password)
|
||||
if authData == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("AuthorizedUser", user)
|
||||
c.Set("Authorization", *authData)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
}
|
||||
@@ -103,8 +114,8 @@ func (api *API) authWebAppMiddleware(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
|
||||
// Check Session
|
||||
if user, ok := getSession(session); ok == true {
|
||||
c.Set("AuthorizedUser", user)
|
||||
if auth, ok := getSession(session); ok == true {
|
||||
c.Set("Authorization", auth)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
return
|
||||
@@ -115,6 +126,20 @@ func (api *API) authWebAppMiddleware(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
func (api *API) authAdminWebAppMiddleware(c *gin.Context) {
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth := data.(authData)
|
||||
if auth.IsAdmin == true {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errorPage(c, http.StatusUnauthorized, "Admin Permissions Required")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
func (api *API) appAuthFormLogin(c *gin.Context) {
|
||||
templateVars := api.getBaseTemplateVars("login", c)
|
||||
|
||||
@@ -129,7 +154,8 @@ func (api *API) appAuthFormLogin(c *gin.Context) {
|
||||
|
||||
// MD5 - KOSync Compatiblity
|
||||
password := fmt.Sprintf("%x", md5.Sum([]byte(rawPassword)))
|
||||
if authorized := api.authorizeCredentials(username, password); authorized != true {
|
||||
authData := api.authorizeCredentials(username, password)
|
||||
if authData == nil {
|
||||
templateVars["Error"] = "Invalid Credentials"
|
||||
c.HTML(http.StatusUnauthorized, "page/login", templateVars)
|
||||
return
|
||||
@@ -137,7 +163,7 @@ func (api *API) appAuthFormLogin(c *gin.Context) {
|
||||
|
||||
// Set Session
|
||||
session := sessions.Default(c)
|
||||
if err := setSession(session, username); err != nil {
|
||||
if err := setSession(session, *authData); err != nil {
|
||||
templateVars["Error"] = "Invalid Credentials"
|
||||
c.HTML(http.StatusUnauthorized, "page/login", templateVars)
|
||||
return
|
||||
@@ -194,9 +220,22 @@ func (api *API) appAuthFormRegister(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get User
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, username)
|
||||
if err != nil {
|
||||
log.Error("[appAuthFormRegister] GetUser DB Error:", err)
|
||||
templateVars["Error"] = "Registration Disabled or User Already Exists"
|
||||
c.HTML(http.StatusBadRequest, "page/login", templateVars)
|
||||
return
|
||||
}
|
||||
|
||||
// Set Session
|
||||
auth := authData{
|
||||
UserName: user.ID,
|
||||
IsAdmin: user.Admin,
|
||||
}
|
||||
session := sessions.Default(c)
|
||||
if err := setSession(session, username); err != nil {
|
||||
if err := setSession(session, auth); err != nil {
|
||||
errorPage(c, http.StatusUnauthorized, "Unauthorized.")
|
||||
return
|
||||
}
|
||||
@@ -212,26 +251,35 @@ func (api *API) appAuthLogout(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
}
|
||||
|
||||
func getSession(session sessions.Session) (user string, ok bool) {
|
||||
func getSession(session sessions.Session) (auth authData, ok bool) {
|
||||
// Check Session
|
||||
authorizedUser := session.Get("authorizedUser")
|
||||
if authorizedUser == nil {
|
||||
return "", false
|
||||
isAdmin := session.Get("isAdmin")
|
||||
expiresAt := session.Get("expiresAt")
|
||||
if authorizedUser == nil || isAdmin == nil || expiresAt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create Auth Object
|
||||
auth = authData{
|
||||
UserName: authorizedUser.(string),
|
||||
IsAdmin: isAdmin.(bool),
|
||||
}
|
||||
|
||||
// Refresh
|
||||
expiresAt := session.Get("expiresAt")
|
||||
if expiresAt != nil && expiresAt.(int64)-time.Now().Unix() < 60*60*24 {
|
||||
if expiresAt.(int64)-time.Now().Unix() < 60*60*24 {
|
||||
log.Info("[getSession] Refreshing Session")
|
||||
setSession(session, authorizedUser.(string))
|
||||
setSession(session, auth)
|
||||
}
|
||||
|
||||
return authorizedUser.(string), true
|
||||
// Authorized
|
||||
return auth, true
|
||||
}
|
||||
|
||||
func setSession(session sessions.Session, user string) error {
|
||||
func setSession(session sessions.Session, auth authData) error {
|
||||
// Set Session Cookie
|
||||
session.Set("authorizedUser", user)
|
||||
session.Set("authorizedUser", auth.UserName)
|
||||
session.Set("isAdmin", auth.IsAdmin)
|
||||
session.Set("expiresAt", time.Now().Unix()+(60*60*24*7))
|
||||
return session.Save()
|
||||
}
|
||||
|
||||
@@ -129,7 +129,10 @@ func (api *API) koCreateUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) koSetProgress(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
var rPosition requestPosition
|
||||
if err := c.ShouldBindJSON(&rPosition); err != nil {
|
||||
@@ -141,7 +144,7 @@ func (api *API) koSetProgress(c *gin.Context) {
|
||||
// Upsert Device
|
||||
if _, err := api.DB.Queries.UpsertDevice(api.DB.Ctx, database.UpsertDeviceParams{
|
||||
ID: rPosition.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DeviceName: rPosition.Device,
|
||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||
}); err != nil {
|
||||
@@ -160,7 +163,7 @@ func (api *API) koSetProgress(c *gin.Context) {
|
||||
Percentage: rPosition.Percentage,
|
||||
DocumentID: rPosition.DocumentID,
|
||||
DeviceID: rPosition.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
Progress: rPosition.Progress,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -171,7 +174,7 @@ func (api *API) koSetProgress(c *gin.Context) {
|
||||
|
||||
// Update Statistic
|
||||
log.Info("[koSetProgress] UpdateDocumentUserStatistic Running...")
|
||||
if err := api.DB.UpdateDocumentUserStatistic(rPosition.DocumentID, rUser.(string)); err != nil {
|
||||
if err := api.DB.UpdateDocumentUserStatistic(rPosition.DocumentID, auth.UserName); err != nil {
|
||||
log.Error("[koSetProgress] UpdateDocumentUserStatistic Error:", err)
|
||||
}
|
||||
log.Info("[koSetProgress] UpdateDocumentUserStatistic Complete")
|
||||
@@ -183,7 +186,10 @@ func (api *API) koSetProgress(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) koGetProgress(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
@@ -194,7 +200,7 @@ func (api *API) koGetProgress(c *gin.Context) {
|
||||
|
||||
progress, err := api.DB.Queries.GetDocumentProgress(api.DB.Ctx, database.GetDocumentProgressParams{
|
||||
DocumentID: rDocID.DocumentID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
})
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -217,7 +223,10 @@ func (api *API) koGetProgress(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) koAddActivities(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
var rActivity requestActivity
|
||||
if err := c.ShouldBindJSON(&rActivity); err != nil {
|
||||
@@ -259,7 +268,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
||||
// Upsert Device
|
||||
if _, err = qtx.UpsertDevice(api.DB.Ctx, database.UpsertDeviceParams{
|
||||
ID: rActivity.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DeviceName: rActivity.Device,
|
||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||
}); err != nil {
|
||||
@@ -271,7 +280,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
||||
// Add All Activity
|
||||
for _, item := range rActivity.Activity {
|
||||
if _, err := qtx.AddActivity(api.DB.Ctx, database.AddActivityParams{
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DocumentID: item.DocumentID,
|
||||
DeviceID: rActivity.DeviceID,
|
||||
StartTime: time.Unix(int64(item.StartTime), 0).UTC().Format(time.RFC3339),
|
||||
@@ -295,7 +304,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
||||
// Update Statistic
|
||||
for _, doc := range allDocuments {
|
||||
log.Info("[koAddActivities] UpdateDocumentUserStatistic Running...")
|
||||
if err := api.DB.UpdateDocumentUserStatistic(doc, rUser.(string)); err != nil {
|
||||
if err := api.DB.UpdateDocumentUserStatistic(doc, auth.UserName); err != nil {
|
||||
log.Error("[koAddActivities] UpdateDocumentUserStatistic Error:", err)
|
||||
}
|
||||
log.Info("[koAddActivities] UpdateDocumentUserStatistic Complete")
|
||||
@@ -307,7 +316,10 @@ func (api *API) koAddActivities(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) koCheckActivitySync(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
var rCheckActivity requestCheckActivitySync
|
||||
if err := c.ShouldBindJSON(&rCheckActivity); err != nil {
|
||||
@@ -319,7 +331,7 @@ func (api *API) koCheckActivitySync(c *gin.Context) {
|
||||
// Upsert Device
|
||||
if _, err := api.DB.Queries.UpsertDevice(api.DB.Ctx, database.UpsertDeviceParams{
|
||||
ID: rCheckActivity.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DeviceName: rCheckActivity.Device,
|
||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||
}); err != nil {
|
||||
@@ -330,7 +342,7 @@ func (api *API) koCheckActivitySync(c *gin.Context) {
|
||||
|
||||
// Get Last Device Activity
|
||||
lastActivity, err := api.DB.Queries.GetLastActivity(api.DB.Ctx, database.GetLastActivityParams{
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DeviceID: rCheckActivity.DeviceID,
|
||||
})
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -405,7 +417,10 @@ func (api *API) koAddDocuments(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
var rCheckDocs requestCheckDocumentSync
|
||||
if err := c.ShouldBindJSON(&rCheckDocs); err != nil {
|
||||
@@ -417,7 +432,7 @@ func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
||||
// Upsert Device
|
||||
_, err := api.DB.Queries.UpsertDevice(api.DB.Ctx, database.UpsertDeviceParams{
|
||||
ID: rCheckDocs.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
UserID: auth.UserName,
|
||||
DeviceName: rCheckDocs.Device,
|
||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||
})
|
||||
|
||||
@@ -61,9 +61,9 @@ func (api *API) opdsEntry(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (api *API) opdsDocuments(c *gin.Context) {
|
||||
var userID string
|
||||
if rUser, _ := c.Get("AuthorizedUser"); rUser != nil {
|
||||
userID = rUser.(string)
|
||||
var auth authData
|
||||
if data, _ := c.Get("Authorization"); data != nil {
|
||||
auth = data.(authData)
|
||||
}
|
||||
|
||||
// Potential URL Parameters (Default Pagination - 100)
|
||||
@@ -78,7 +78,7 @@ func (api *API) opdsDocuments(c *gin.Context) {
|
||||
|
||||
// Get Documents
|
||||
documents, err := api.DB.Queries.GetDocumentsWithStats(api.DB.Ctx, database.GetDocumentsWithStatsParams{
|
||||
UserID: userID,
|
||||
UserID: auth.UserName,
|
||||
Query: query,
|
||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||
Limit: *qParams.Limit,
|
||||
|
||||
Reference in New Issue
Block a user