activity: implement most initial logging

resetPassword, changePassword, delete/createInvite, enable/disable,
creation/deletion of invites & users are all done, only remaining one is
account linking.
This commit is contained in:
Harvey Tindall 2023-10-19 18:56:35 +01:00
parent 2c787b4d46
commit b620c0d9ae
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
7 changed files with 120 additions and 4 deletions

View File

@ -85,6 +85,13 @@ func (app *appContext) checkInvites() {
wait.Wait() wait.Wait()
} }
app.storage.DeleteInvitesKey(data.Code) app.storage.DeleteInvitesKey(data.Code)
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityDaemon,
InviteCode: data.Code,
Time: time.Now(),
})
} }
} }
@ -130,12 +137,24 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
} }
match = false match = false
app.storage.DeleteInvitesKey(code) app.storage.DeleteInvitesKey(code)
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityDaemon,
InviteCode: code,
Time: time.Now(),
})
} else if used { } else if used {
del := false del := false
newInv := inv newInv := inv
if newInv.RemainingUses == 1 { if newInv.RemainingUses == 1 {
del = true del = true
app.storage.DeleteInvitesKey(code) app.storage.DeleteInvitesKey(code)
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityDaemon,
InviteCode: code,
Time: time.Now(),
})
} else if newInv.RemainingUses != 0 { } else if newInv.RemainingUses != 0 {
// 0 means infinite i guess? // 0 means infinite i guess?
newInv.RemainingUses-- newInv.RemainingUses--
@ -236,6 +255,17 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
} }
} }
app.storage.SetInvitesKey(invite.Code, invite) app.storage.SetInvitesKey(invite.Code, invite)
// Record activity
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityCreateInvite,
UserID: "",
SourceType: ActivityAdmin,
Source: gc.GetString("jfId"),
InviteCode: invite.Code,
Time: time.Now(),
})
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -433,6 +463,15 @@ func (app *appContext) DeleteInvite(gc *gin.Context) {
_, ok = app.storage.GetInvitesKey(req.Code) _, ok = app.storage.GetInvitesKey(req.Code)
if ok { if ok {
app.storage.DeleteInvitesKey(req.Code) app.storage.DeleteInvitesKey(req.Code)
// Record activity
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityAdmin,
Source: gc.GetString("jfId"),
Time: time.Now(),
})
app.info.Printf("%s: Invite deleted", req.Code) app.info.Printf("%s: Invite deleted", req.Code)
respondBool(200, true, gc) respondBool(200, true, gc)
return return

View File

@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
) )
@ -620,6 +621,15 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityChangePassword,
UserID: user.ID,
SourceType: ActivityUser,
Source: user.ID,
Time: time.Now(),
})
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
func() { func() {
ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId")) ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId"))

View File

@ -567,6 +567,10 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
sendMail = false sendMail = false
} }
} }
activityType := ActivityDisabled
if req.Enabled {
activityType = ActivityEnabled
}
for _, userID := range req.Users { for _, userID := range req.Users {
user, status, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if status != 200 || err != nil { if status != 200 || err != nil {
@ -581,6 +585,16 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
app.err.Printf("Failed to set policy for user \"%s\" (%d): %v", userID, status, err) app.err.Printf("Failed to set policy for user \"%s\" (%d): %v", userID, status, err)
continue continue
} }
// Record activity
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: activityType,
UserID: userID,
SourceType: ActivityAdmin,
Source: gc.GetString("jfId"),
Time: time.Now(),
})
if sendMail && req.Notify { if sendMail && req.Notify {
if err := app.sendByID(msg, userID); err != nil { if err := app.sendByID(msg, userID); err != nil {
app.err.Printf("Failed to send account enabled/disabled email: %v", err) app.err.Printf("Failed to send account enabled/disabled email: %v", err)
@ -642,6 +656,16 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
errors[userID] += msg errors[userID] += msg
} }
} }
// Record activity
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeletion,
UserID: userID,
SourceType: ActivityAdmin,
Source: gc.GetString("jfId"),
Time: time.Now(),
})
if sendMail && req.Notify { if sendMail && req.Notify {
if err := app.sendByID(msg, userID); err != nil { if err := app.sendByID(msg, userID); err != nil {
app.err.Printf("Failed to send account deletion email: %v", err) app.err.Printf("Failed to send account deletion email: %v", err)

11
api.go
View File

@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/itchyny/timefmt-go" "github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@ -157,6 +158,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
} }
username = resp.UsersReset[0] username = resp.UsersReset[0]
} }
var user mediabrowser.User var user mediabrowser.User
var status int var status int
var err error var err error
@ -170,6 +172,15 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityResetPassword,
UserID: user.ID,
SourceType: ActivityUser,
Source: user.ID,
Time: time.Now(),
})
prevPassword := req.PIN prevPassword := req.PIN
if isInternal { if isInternal {
prevPassword = "" prevPassword = ""

View File

@ -38,14 +38,15 @@ const (
type ActivitySource int type ActivitySource int
const ( const (
ActivityUser ActivitySource = iota // Source = UserID. For ActivityCreation, this would mean the referrer. ActivityUser ActivitySource = iota // Source = UserID. For ActivityCreation, this would mean the referrer.
ActivityAdmin // Source = Admin's UserID, or simply just "admin" ActivityAdmin // Source = Admin's UserID, or blank if jellyfin login isn't on.
ActivityAnon // Source = Blank, or potentially browser info. For ActivityCreation, this would be via an invite ActivityAnon // Source = Blank, or potentially browser info. For ActivityCreation, this would be via an invite
ActivityDaemon // Source = Blank, was deleted/disabled due to expiry by daemon
) )
type Activity struct { type Activity struct {
Type ActivityType `badgerhold:"index"` Type ActivityType `badgerhold:"index"`
UserID string UserID string // ID of target user. For account creation, this will be the newly created account
SourceType ActivitySource SourceType ActivitySource
Source string Source string
InviteCode string // Only set for ActivityCreation InviteCode string // Only set for ActivityCreation

View File

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
) )
type userDaemon struct { type userDaemon struct {
@ -95,18 +96,31 @@ func (app *appContext) checkUsers() {
continue continue
} }
app.info.Printf("%s expired user \"%s\"", termPlural, user.Name) app.info.Printf("%s expired user \"%s\"", termPlural, user.Name)
// Record activity
activity := Activity{
UserID: id,
SourceType: ActivityDaemon,
Time: time.Now(),
}
if mode == "delete" { if mode == "delete" {
status, err = app.jf.DeleteUser(id) status, err = app.jf.DeleteUser(id)
activity.Type = ActivityDeletion
} else if mode == "disable" { } else if mode == "disable" {
user.Policy.IsDisabled = true user.Policy.IsDisabled = true
// Admins can't be disabled // Admins can't be disabled
user.Policy.IsAdministrator = false user.Policy.IsAdministrator = false
status, err = app.jf.SetPolicy(id, user.Policy) status, err = app.jf.SetPolicy(id, user.Policy)
activity.Type = ActivityDisabled
} }
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to %s \"%s\" (%d): %s", mode, user.Name, status, err) app.err.Printf("Failed to %s \"%s\" (%d): %s", mode, user.Name, status, err)
continue continue
} }
app.storage.SetActivityKey(shortuuid.New(), activity)
app.storage.DeleteUserExpiryKey(expiry.JellyfinID) app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
if contact { if contact {

View File

@ -17,6 +17,7 @@ import (
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
"github.com/steambap/captcha" "github.com/steambap/captcha"
) )
@ -329,6 +330,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
} }
username = pwr.Username username = pwr.Username
} }
if (status == 200 || status == 204) && err == nil && (isInternal || resp.Success) { if (status == 200 || status == 204) && err == nil && (isInternal || resp.Success) {
data["success"] = true data["success"] = true
data["pin"] = pin data["pin"] = pin
@ -338,6 +340,21 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
} else { } else {
app.err.Printf("Password Reset failed (%d): %v", status, err) app.err.Printf("Password Reset failed (%d): %v", status, err)
} }
// Only log PWRs we know the user for.
if username != "" {
jfUser, status, err := app.jf.UserByName(username, false)
if err == nil && status == 200 {
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityResetPassword,
UserID: jfUser.ID,
SourceType: ActivityUser,
Source: jfUser.ID,
Time: time.Now(),
})
}
}
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
jfUser, status, err := app.jf.UserByName(username, false) jfUser, status, err := app.jf.UserByName(username, false)
if status != 200 || err != nil { if status != 200 || err != nil {