From f823705e40f8e821fc71baee5f7d528f2cd4f2b7 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sat, 23 Dec 2023 21:47:41 +0000 Subject: [PATCH] ips: log on activities, show on card --- api-activities.go | 1 + api-invites.go | 10 +++++----- api-messages.go | 15 +++++++++++---- api-userpage.go | 16 ++++++++-------- api-users.go | 14 +++++++------- api.go | 2 +- auth.go | 19 ++++++++----------- config/config-base.json | 6 +++--- models.go | 1 + storage.go | 8 +++++++- ts/modules/activity.ts | 25 ++++++++++++++++++++----- user-auth.go | 4 ++-- userdaemon.go | 2 +- views.go | 4 ++-- 14 files changed, 77 insertions(+), 50 deletions(-) diff --git a/api-activities.go b/api-activities.go index 929a6f5..17e24ad 100644 --- a/api-activities.go +++ b/api-activities.go @@ -138,6 +138,7 @@ func (app *appContext) GetActivities(gc *gin.Context) { InviteCode: act.InviteCode, Value: act.Value, Time: act.Time.Unix(), + IP: act.IP, } if act.Type == ActivityDeletion || act.Type == ActivityCreation { resp.Activities[i].Username = act.Value diff --git a/api-invites.go b/api-invites.go index eb6e810..d17b8e3 100644 --- a/api-invites.go +++ b/api-invites.go @@ -102,7 +102,7 @@ func (app *appContext) checkInvites() { InviteCode: data.Code, Value: data.Label, Time: time.Now(), - }) + }, nil, false) } } @@ -161,7 +161,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool InviteCode: code, Value: inv.Label, Time: time.Now(), - }) + }, nil, false) } else if used { del := false newInv := inv @@ -174,7 +174,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool InviteCode: code, Value: inv.Label, Time: time.Now(), - }) + }, nil, false) } else if newInv.RemainingUses != 0 { // 0 means infinite i guess? newInv.RemainingUses-- @@ -285,7 +285,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { InviteCode: invite.Code, Value: invite.Label, Time: time.Now(), - }) + }, gc, false) respondBool(200, true, gc) } @@ -492,7 +492,7 @@ func (app *appContext) DeleteInvite(gc *gin.Context) { InviteCode: req.Code, Value: inv.Label, Time: time.Now(), - }) + }, gc, false) app.info.Printf("%s: Invite deleted", req.Code) respondBool(200, true, gc) diff --git a/api-messages.go b/api-messages.go index fcb14b9..452dc57 100644 --- a/api-messages.go +++ b/api-messages.go @@ -573,6 +573,7 @@ func (app *appContext) MatrixCheckPIN(gc *gin.Context) { // @Failure 500 {object} boolResponse // @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password." // @Router /matrix/login [post] +// @Security Bearer // @tags Other func (app *appContext) MatrixLogin(gc *gin.Context) { var req MatrixLoginDTO @@ -608,6 +609,7 @@ func (app *appContext) MatrixLogin(gc *gin.Context) { // @Failure 500 {object} boolResponse // @Param MatrixConnectUserDTO body MatrixConnectUserDTO true "User's Jellyfin ID & Matrix user ID." // @Router /users/matrix [post] +// @Security Bearer // @tags Other func (app *appContext) MatrixConnect(gc *gin.Context) { var req MatrixConnectUserDTO @@ -639,6 +641,7 @@ func (app *appContext) MatrixConnect(gc *gin.Context) { // @Failure 500 {object} boolResponse // @Param username path string true "username to search." // @Router /users/discord/{username} [get] +// @Security Bearer // @tags Other func (app *appContext) DiscordGetUsers(gc *gin.Context) { name := gc.Param("username") @@ -665,6 +668,7 @@ func (app *appContext) DiscordGetUsers(gc *gin.Context) { // @Failure 500 {object} boolResponse // @Param DiscordConnectUserDTO body DiscordConnectUserDTO true "User's Jellyfin ID & Discord ID." // @Router /users/discord [post] +// @Security Bearer // @tags Other func (app *appContext) DiscordConnect(gc *gin.Context) { var req DiscordConnectUserDTO @@ -688,7 +692,7 @@ func (app *appContext) DiscordConnect(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "discord", Time: time.Now(), - }) + }, gc, false) linkExistingOmbiDiscordTelegram(app) respondBool(200, true, gc) @@ -699,6 +703,7 @@ func (app *appContext) DiscordConnect(gc *gin.Context) { // @Success 200 {object} boolResponse // @Param forUserDTO body forUserDTO true "User's Jellyfin ID." // @Router /users/discord [delete] +// @Security Bearer // @Tags Users func (app *appContext) UnlinkDiscord(gc *gin.Context) { var req forUserDTO @@ -717,7 +722,7 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "discord", Time: time.Now(), - }) + }, gc, false) respondBool(200, true, gc) } @@ -727,6 +732,7 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) { // @Success 200 {object} boolResponse // @Param forUserDTO body forUserDTO true "User's Jellyfin ID." // @Router /users/telegram [delete] +// @Security Bearer // @Tags Users func (app *appContext) UnlinkTelegram(gc *gin.Context) { var req forUserDTO @@ -745,7 +751,7 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "telegram", Time: time.Now(), - }) + }, gc, false) respondBool(200, true, gc) } @@ -755,6 +761,7 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) { // @Success 200 {object} boolResponse // @Param forUserDTO body forUserDTO true "User's Jellyfin ID." // @Router /users/matrix [delete] +// @Security Bearer // @Tags Users func (app *appContext) UnlinkMatrix(gc *gin.Context) { var req forUserDTO @@ -773,7 +780,7 @@ func (app *appContext) UnlinkMatrix(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "matrix", Time: time.Now(), - }) + }, gc, false) respondBool(200, true, gc) } diff --git a/api-userpage.go b/api-userpage.go index ea7adb7..ee7eb33 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -216,7 +216,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) { Source: gc.GetString("jfId"), Value: "email", Time: time.Now(), - }) + }, gc, true) if app.config.Section("ombi").Key("enabled").MustBool(false) { ombiUser, code, err := app.getOmbiUser(id) @@ -378,7 +378,7 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "discord", Time: time.Now(), - }) + }, gc, true) respondBool(200, true, gc) } @@ -426,7 +426,7 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "telegram", Time: time.Now(), - }) + }, gc, true) respondBool(200, true, gc) } @@ -507,7 +507,7 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "matrix", Time: time.Now(), - }) + }, gc, true) delete(app.matrix.tokens, pin) respondBool(200, true, gc) @@ -529,7 +529,7 @@ func (app *appContext) UnlinkMyDiscord(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "discord", Time: time.Now(), - }) + }, gc, true) respondBool(200, true, gc) } @@ -550,7 +550,7 @@ func (app *appContext) UnlinkMyTelegram(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "telegram", Time: time.Now(), - }) + }, gc, true) respondBool(200, true, gc) } @@ -571,7 +571,7 @@ func (app *appContext) UnlinkMyMatrix(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "matrix", Time: time.Now(), - }) + }, gc, true) respondBool(200, true, gc) } @@ -701,7 +701,7 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) { SourceType: ActivityUser, Source: user.ID, Time: time.Now(), - }) + }, gc, true) if app.config.Section("ombi").Key("enabled").MustBool(false) { func() { diff --git a/api-users.go b/api-users.go index 138c287..b13b2f2 100644 --- a/api-users.go +++ b/api-users.go @@ -55,7 +55,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { Source: gc.GetString("jfId"), Value: user.Name, Time: time.Now(), - }) + }, gc, false) profile := app.storage.GetDefaultProfile() if req.Profile != "" && req.Profile != "none" { @@ -114,7 +114,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { type errorFunc func(gc *gin.Context) // Used on the form & when a users email has been confirmed. -func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, success bool) { +func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context) (f errorFunc, success bool) { existingUser, _, _ := app.jf.UserByName(req.Username, false) if existingUser.Name != "" { f = func(gc *gin.Context) { @@ -331,7 +331,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc InviteCode: invite.Code, Value: user.Name, Time: time.Now(), - }) + }, gc, true) emailStore := EmailAddress{ Addr: req.Email, @@ -539,7 +539,7 @@ func (app *appContext) NewUser(gc *gin.Context) { return } } - f, success := app.newUser(req, false) + f, success := app.newUser(req, false, gc) if !success { f(gc) return @@ -609,7 +609,7 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) { SourceType: ActivityAdmin, Source: gc.GetString("jfId"), Time: time.Now(), - }) + }, gc, false) if sendMail && req.Notify { if err := app.sendByID(msg, userID); err != nil { @@ -687,7 +687,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) { Source: gc.GetString("jfId"), Value: username, Time: time.Now(), - }) + }, gc, false) if sendMail && req.Notify { if err := app.sendByID(msg, userID); err != nil { @@ -1208,7 +1208,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { Source: gc.GetString("jfId"), Value: "email", Time: time.Now(), - }) + }, gc, false) if ombiEnabled { ombiUser, code, err := app.getOmbiUser(id) diff --git a/api.go b/api.go index 1b926e0..c1fea0a 100644 --- a/api.go +++ b/api.go @@ -179,7 +179,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) { SourceType: ActivityUser, Source: user.ID, Time: time.Now(), - }) + }, gc, true) prevPassword := req.PIN if isInternal { diff --git a/auth.go b/auth.go index 64635c1..877d7ab 100644 --- a/auth.go +++ b/auth.go @@ -19,25 +19,22 @@ const ( ) func (app *appContext) logIpInfo(gc *gin.Context, user bool, out string) { - app.info.Printf(out) if (user && LOGIPU) || (!user && LOGIP) { - app.info.Printf(" (ip=%s)", strings.TrimSpace(gc.Request.Header.Get("X-Real-IP"))) + out += fmt.Sprintf(" (ip=%s)", gc.ClientIP()) } - app.info.Print("\n") + app.info.Println(out) } func (app *appContext) logIpDebug(gc *gin.Context, user bool, out string) { - app.debug.Printf(out) if (user && LOGIPU) || (!user && LOGIP) { - app.debug.Printf(" (ip=%s)", strings.TrimSpace(gc.Request.Header.Get("X-Real-IP"))) + out += fmt.Sprintf(" (ip=%s)", gc.ClientIP()) } - app.debug.Print("\n") + app.debug.Println(out) } func (app *appContext) logIpErr(gc *gin.Context, user bool, out string) { - app.err.Printf(out) if (user && LOGIPU) || (!user && LOGIP) { - app.err.Printf(" (ip=%s)", strings.TrimSpace(gc.Request.Header.Get("X-Real-IP"))) + out += fmt.Sprintf(" (ip=%s)", gc.ClientIP()) } - app.err.Print("\n") + app.err.Println(out) } func (app *appContext) webAuth() gin.HandlerFunc { @@ -202,7 +199,7 @@ func (app *appContext) validateJellyfinCredentials(username, password string, gc // @tags Auth // @Security getTokenAuth func (app *appContext) getTokenLogin(gc *gin.Context) { - app.info.Println("Token requested (login attempt)") + app.logIpInfo(gc, false, "Token requested (login attempt)") username, password, ok := app.decodeValidateLoginHeader(gc, false) if !ok { return @@ -307,7 +304,7 @@ func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName s // @Router /token/refresh [get] // @tags Auth func (app *appContext) getTokenRefresh(gc *gin.Context) { - app.debug.Println("Token requested (refresh token)") + app.logIpInfo(gc, false, "Token requested (refresh token)") claims, ok := app.decodeValidateRefreshCookie(gc, "refresh") if !ok { return diff --git a/config/config-base.json b/config/config-base.json index 1f19920..e09ff14 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -303,15 +303,15 @@ "requires_restart": true, "type": "bool", "value": false, - "description": "Log IP addresses in console and in activities. See notice below on legality." + "description": "Log IP addresses of admins and admin page requests in console and in activities. See notice below on legality." }, - "log_ips_userpage": { + "log_ips_users": { "name": "Log IPs accessing User Page", "required": false, "requires_restart": true, "type": "bool", "value": false, - "description": "Log IP addresses in console and in activities. See notice below on legality." + "description": "Log IP addresses of users in console and in activities. See notice below on legality." }, "ip_note": { "name": "Logging IPs:", diff --git a/models.go b/models.go index ba46f7b..b7f5f7b 100644 --- a/models.go +++ b/models.go @@ -444,6 +444,7 @@ type ActivityDTO struct { InviteCode string `json:"invite_code"` Value string `json:"value"` Time int64 `json:"time"` + IP string `json:"ip"` } type GetActivitiesDTO struct { diff --git a/storage.go b/storage.go index 19067ba..1bc6b01 100644 --- a/storage.go +++ b/storage.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/gin-gonic/gin" "github.com/hrfee/jfa-go/logger" "github.com/hrfee/mediabrowser" "github.com/timshannon/badgerhold/v4" @@ -55,6 +56,7 @@ type Activity struct { InviteCode string // Set for ActivityCreation, create/deleteInvite Value string // Used for ActivityContactLinked where it's "email/discord/telegram/matrix", Create/DeleteInvite, where it's the label, and Creation/Deletion, where it's the Username. Time time.Time + IP string } type UserExpiry struct { @@ -563,8 +565,12 @@ func (st *Storage) GetActivityKey(k string) (Activity, bool) { } // SetActivityKey stores value v in key k. -func (st *Storage) SetActivityKey(k string, v Activity) { +// If the IP should be logged, pass "gc", and whether or not the action is of a user +func (st *Storage) SetActivityKey(k string, v Activity, gc *gin.Context, user bool) { v.ID = k + if gc != nil && ((LOGIPU && user) || (LOGIP && !user)) { + v.IP = gc.ClientIP() + } err := st.db.Upsert(k, v) if err != nil { // fmt.Printf("Failed to set custom content: %v\n", err) diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts index 6bf2f2f..d5bf3d3 100644 --- a/ts/modules/activity.ts +++ b/ts/modules/activity.ts @@ -14,6 +14,7 @@ export interface activity { time: number; username: string; source_username: string; + ip: string; } var activityTypeMoods = { @@ -43,6 +44,7 @@ export class Activity implements activity, SearchableItem { private _referrer: HTMLElement; private _expiryTypeBadge: HTMLElement; private _delete: HTMLElement; + private _ip: HTMLElement; private _act: activity; private _urlBase: string = ((): string => { let link = window.location.href; @@ -205,6 +207,16 @@ export class Activity implements activity, SearchableItem { } } + get ip(): string { return this._act.ip; } + set ip(v: string) { + this._act.ip = v; + if (v) { + this._ip.innerHTML = `IP${v}`; + } else { + this._ip.textContent = ``; + } + } + get invite_code(): string { return this._act.invite_code; } set invite_code(v: string) { this._act.invite_code = v; @@ -260,12 +272,13 @@ export class Activity implements activity, SearchableItem { -
-
- -
-
+
+
+
+ +
+
@@ -277,6 +290,7 @@ export class Activity implements activity, SearchableItem { this._time = this._card.querySelector(".activity-time"); this._sourceType = this._card.querySelector(".activity-source-type"); this._source = this._card.querySelector(".activity-source"); + this._ip = this._card.querySelector(".activity-ip"); this._referrer = this._card.querySelector(".activity-referrer"); this._expiryTypeBadge = this._card.querySelector(".activity-expiry-type"); this._delete = this._card.querySelector(".activity-delete"); @@ -324,6 +338,7 @@ export class Activity implements activity, SearchableItem { this.source = act.source; this.value = act.value; this.type = act.type; + this.ip = act.ip; } delete = () => _delete("/activity/" + this._act.id, null, (req: XMLHttpRequest) => { diff --git a/user-auth.go b/user-auth.go index 518d6c6..d347f08 100644 --- a/user-auth.go +++ b/user-auth.go @@ -45,7 +45,7 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) { respond(500, "Contact Admin", gc) return } - app.info.Println("UserToken requested (login attempt)") + app.logIpInfo(gc, true, "UserToken requested (login attempt)") username, password, ok := app.decodeValidateLoginHeader(gc, true) if !ok { return @@ -86,7 +86,7 @@ func (app *appContext) getUserTokenRefresh(gc *gin.Context) { return } - app.info.Println("UserToken request (refresh token)") + app.logIpInfo(gc, true, "UserToken request (refresh token)") claims, ok := app.decodeValidateRefreshCookie(gc, "user-refresh") if !ok { return diff --git a/userdaemon.go b/userdaemon.go index af3b860..abe8212 100644 --- a/userdaemon.go +++ b/userdaemon.go @@ -120,7 +120,7 @@ func (app *appContext) checkUsers() { continue } - app.storage.SetActivityKey(shortuuid.New(), activity) + app.storage.SetActivityKey(shortuuid.New(), activity, nil, false) app.storage.DeleteUserExpiryKey(expiry.JellyfinID) app.jf.CacheExpiry = time.Now() diff --git a/views.go b/views.go index 51ccca8..ff1131d 100644 --- a/views.go +++ b/views.go @@ -361,7 +361,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) { SourceType: ActivityUser, Source: jfUser.ID, Time: time.Now(), - }) + }, gc, true) } } @@ -611,7 +611,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) { app.debug.Printf("Invalid key") return } - f, success := app.newUser(req, true) + f, success := app.newUser(req, true, gc) if !success { app.err.Printf("Failed to create new user") // Not meant for us. Calling this will be a mess, but at least it might give us some information.