mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-28 03:50:10 +00:00
Compare commits
8 Commits
49d8c6f8e4
...
4f5d12f800
Author | SHA1 | Date | |
---|---|---|---|
4f5d12f800 | |||
9092b98b28 | |||
0f72a85724 | |||
0840931fed | |||
00379824df | |||
f823705e40 | |||
269836fc99 | |||
|
04c94ba55a |
@ -138,6 +138,7 @@ func (app *appContext) GetActivities(gc *gin.Context) {
|
|||||||
InviteCode: act.InviteCode,
|
InviteCode: act.InviteCode,
|
||||||
Value: act.Value,
|
Value: act.Value,
|
||||||
Time: act.Time.Unix(),
|
Time: act.Time.Unix(),
|
||||||
|
IP: act.IP,
|
||||||
}
|
}
|
||||||
if act.Type == ActivityDeletion || act.Type == ActivityCreation {
|
if act.Type == ActivityDeletion || act.Type == ActivityCreation {
|
||||||
resp.Activities[i].Username = act.Value
|
resp.Activities[i].Username = act.Value
|
||||||
|
@ -102,7 +102,7 @@ func (app *appContext) checkInvites() {
|
|||||||
InviteCode: data.Code,
|
InviteCode: data.Code,
|
||||||
Value: data.Label,
|
Value: data.Label,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, nil, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
|
|||||||
InviteCode: code,
|
InviteCode: code,
|
||||||
Value: inv.Label,
|
Value: inv.Label,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, nil, false)
|
||||||
} else if used {
|
} else if used {
|
||||||
del := false
|
del := false
|
||||||
newInv := inv
|
newInv := inv
|
||||||
@ -174,7 +174,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
|
|||||||
InviteCode: code,
|
InviteCode: code,
|
||||||
Value: inv.Label,
|
Value: inv.Label,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, nil, false)
|
||||||
} else if newInv.RemainingUses != 0 {
|
} else if newInv.RemainingUses != 0 {
|
||||||
// 0 means infinite i guess?
|
// 0 means infinite i guess?
|
||||||
newInv.RemainingUses--
|
newInv.RemainingUses--
|
||||||
@ -285,7 +285,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
InviteCode: invite.Code,
|
InviteCode: invite.Code,
|
||||||
Value: invite.Label,
|
Value: invite.Label,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -305,7 +305,8 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
if inv.IsReferral {
|
if inv.IsReferral {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
years, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||||
|
months += years * 12
|
||||||
invite := inviteDTO{
|
invite := inviteDTO{
|
||||||
Code: inv.Code,
|
Code: inv.Code,
|
||||||
Months: months,
|
Months: months,
|
||||||
@ -492,7 +493,7 @@ func (app *appContext) DeleteInvite(gc *gin.Context) {
|
|||||||
InviteCode: req.Code,
|
InviteCode: req.Code,
|
||||||
Value: inv.Label,
|
Value: inv.Label,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
app.info.Printf("%s: Invite deleted", req.Code)
|
app.info.Printf("%s: Invite deleted", req.Code)
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
|
@ -573,6 +573,7 @@ func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
|
|||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password."
|
// @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password."
|
||||||
// @Router /matrix/login [post]
|
// @Router /matrix/login [post]
|
||||||
|
// @Security Bearer
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) MatrixLogin(gc *gin.Context) {
|
func (app *appContext) MatrixLogin(gc *gin.Context) {
|
||||||
var req MatrixLoginDTO
|
var req MatrixLoginDTO
|
||||||
@ -608,6 +609,7 @@ func (app *appContext) MatrixLogin(gc *gin.Context) {
|
|||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Param MatrixConnectUserDTO body MatrixConnectUserDTO true "User's Jellyfin ID & Matrix user ID."
|
// @Param MatrixConnectUserDTO body MatrixConnectUserDTO true "User's Jellyfin ID & Matrix user ID."
|
||||||
// @Router /users/matrix [post]
|
// @Router /users/matrix [post]
|
||||||
|
// @Security Bearer
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) MatrixConnect(gc *gin.Context) {
|
func (app *appContext) MatrixConnect(gc *gin.Context) {
|
||||||
var req MatrixConnectUserDTO
|
var req MatrixConnectUserDTO
|
||||||
@ -639,6 +641,7 @@ func (app *appContext) MatrixConnect(gc *gin.Context) {
|
|||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Param username path string true "username to search."
|
// @Param username path string true "username to search."
|
||||||
// @Router /users/discord/{username} [get]
|
// @Router /users/discord/{username} [get]
|
||||||
|
// @Security Bearer
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) DiscordGetUsers(gc *gin.Context) {
|
func (app *appContext) DiscordGetUsers(gc *gin.Context) {
|
||||||
name := gc.Param("username")
|
name := gc.Param("username")
|
||||||
@ -665,6 +668,7 @@ func (app *appContext) DiscordGetUsers(gc *gin.Context) {
|
|||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Param DiscordConnectUserDTO body DiscordConnectUserDTO true "User's Jellyfin ID & Discord ID."
|
// @Param DiscordConnectUserDTO body DiscordConnectUserDTO true "User's Jellyfin ID & Discord ID."
|
||||||
// @Router /users/discord [post]
|
// @Router /users/discord [post]
|
||||||
|
// @Security Bearer
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) DiscordConnect(gc *gin.Context) {
|
func (app *appContext) DiscordConnect(gc *gin.Context) {
|
||||||
var req DiscordConnectUserDTO
|
var req DiscordConnectUserDTO
|
||||||
@ -688,7 +692,7 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "discord",
|
Value: "discord",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
linkExistingOmbiDiscordTelegram(app)
|
linkExistingOmbiDiscordTelegram(app)
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
@ -699,6 +703,7 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
||||||
// @Router /users/discord [delete]
|
// @Router /users/discord [delete]
|
||||||
|
// @Security Bearer
|
||||||
// @Tags Users
|
// @Tags Users
|
||||||
func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
||||||
var req forUserDTO
|
var req forUserDTO
|
||||||
@ -717,7 +722,7 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "discord",
|
Value: "discord",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -727,6 +732,7 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
||||||
// @Router /users/telegram [delete]
|
// @Router /users/telegram [delete]
|
||||||
|
// @Security Bearer
|
||||||
// @Tags Users
|
// @Tags Users
|
||||||
func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
||||||
var req forUserDTO
|
var req forUserDTO
|
||||||
@ -745,7 +751,7 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "telegram",
|
Value: "telegram",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -755,6 +761,7 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
||||||
// @Router /users/matrix [delete]
|
// @Router /users/matrix [delete]
|
||||||
|
// @Security Bearer
|
||||||
// @Tags Users
|
// @Tags Users
|
||||||
func (app *appContext) UnlinkMatrix(gc *gin.Context) {
|
func (app *appContext) UnlinkMatrix(gc *gin.Context) {
|
||||||
var req forUserDTO
|
var req forUserDTO
|
||||||
@ -773,7 +780,7 @@ func (app *appContext) UnlinkMatrix(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "matrix",
|
Value: "matrix",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "email",
|
Value: "email",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
ombiUser, code, err := app.getOmbiUser(id)
|
ombiUser, code, err := app.getOmbiUser(id)
|
||||||
@ -378,7 +378,7 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "discord",
|
Value: "discord",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -426,7 +426,7 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "telegram",
|
Value: "telegram",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -507,7 +507,7 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "matrix",
|
Value: "matrix",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
delete(app.matrix.tokens, pin)
|
delete(app.matrix.tokens, pin)
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
@ -529,7 +529,7 @@ func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "discord",
|
Value: "discord",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -550,7 +550,7 @@ func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "telegram",
|
Value: "telegram",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -571,7 +571,7 @@ func (app *appContext) UnlinkMyMatrix(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "matrix",
|
Value: "matrix",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -701,7 +701,7 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
|
|||||||
SourceType: ActivityUser,
|
SourceType: ActivityUser,
|
||||||
Source: user.ID,
|
Source: user.ID,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
func() {
|
func() {
|
||||||
|
14
api-users.go
14
api-users.go
@ -55,7 +55,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: user.Name,
|
Value: user.Name,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
profile := app.storage.GetDefaultProfile()
|
profile := app.storage.GetDefaultProfile()
|
||||||
if req.Profile != "" && req.Profile != "none" {
|
if req.Profile != "" && req.Profile != "none" {
|
||||||
@ -114,7 +114,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
|||||||
type errorFunc func(gc *gin.Context)
|
type errorFunc func(gc *gin.Context)
|
||||||
|
|
||||||
// Used on the form & when a users email has been confirmed.
|
// 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)
|
existingUser, _, _ := app.jf.UserByName(req.Username, false)
|
||||||
if existingUser.Name != "" {
|
if existingUser.Name != "" {
|
||||||
f = func(gc *gin.Context) {
|
f = func(gc *gin.Context) {
|
||||||
@ -331,7 +331,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
|||||||
InviteCode: invite.Code,
|
InviteCode: invite.Code,
|
||||||
Value: user.Name,
|
Value: user.Name,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
emailStore := EmailAddress{
|
emailStore := EmailAddress{
|
||||||
Addr: req.Email,
|
Addr: req.Email,
|
||||||
@ -539,7 +539,7 @@ func (app *appContext) NewUser(gc *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f, success := app.newUser(req, false)
|
f, success := app.newUser(req, false, gc)
|
||||||
if !success {
|
if !success {
|
||||||
f(gc)
|
f(gc)
|
||||||
return
|
return
|
||||||
@ -609,7 +609,7 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
|
|||||||
SourceType: ActivityAdmin,
|
SourceType: ActivityAdmin,
|
||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
if sendMail && req.Notify {
|
if sendMail && req.Notify {
|
||||||
if err := app.sendByID(msg, userID); err != nil {
|
if err := app.sendByID(msg, userID); err != nil {
|
||||||
@ -687,7 +687,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: username,
|
Value: username,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
if sendMail && req.Notify {
|
if sendMail && req.Notify {
|
||||||
if err := app.sendByID(msg, userID); err != nil {
|
if err := app.sendByID(msg, userID); err != nil {
|
||||||
@ -1208,7 +1208,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
|
|||||||
Source: gc.GetString("jfId"),
|
Source: gc.GetString("jfId"),
|
||||||
Value: "email",
|
Value: "email",
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, false)
|
||||||
|
|
||||||
if ombiEnabled {
|
if ombiEnabled {
|
||||||
ombiUser, code, err := app.getOmbiUser(id)
|
ombiUser, code, err := app.getOmbiUser(id)
|
||||||
|
2
api.go
2
api.go
@ -187,7 +187,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
SourceType: ActivityUser,
|
SourceType: ActivityUser,
|
||||||
Source: user.ID,
|
Source: user.ID,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
|
|
||||||
prevPassword := req.PIN
|
prevPassword := req.PIN
|
||||||
if isInternal {
|
if isInternal {
|
||||||
|
39
auth.go
39
auth.go
@ -18,6 +18,25 @@ const (
|
|||||||
REFRESH_TOKEN_VALIDITY_SEC = 3600 * 24
|
REFRESH_TOKEN_VALIDITY_SEC = 3600 * 24
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (app *appContext) logIpInfo(gc *gin.Context, user bool, out string) {
|
||||||
|
if (user && LOGIPU) || (!user && LOGIP) {
|
||||||
|
out += fmt.Sprintf(" (ip=%s)", gc.ClientIP())
|
||||||
|
}
|
||||||
|
app.info.Println(out)
|
||||||
|
}
|
||||||
|
func (app *appContext) logIpDebug(gc *gin.Context, user bool, out string) {
|
||||||
|
if (user && LOGIPU) || (!user && LOGIP) {
|
||||||
|
out += fmt.Sprintf(" (ip=%s)", gc.ClientIP())
|
||||||
|
}
|
||||||
|
app.debug.Println(out)
|
||||||
|
}
|
||||||
|
func (app *appContext) logIpErr(gc *gin.Context, user bool, out string) {
|
||||||
|
if (user && LOGIPU) || (!user && LOGIP) {
|
||||||
|
out += fmt.Sprintf(" (ip=%s)", gc.ClientIP())
|
||||||
|
}
|
||||||
|
app.err.Println(out)
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) webAuth() gin.HandlerFunc {
|
func (app *appContext) webAuth() gin.HandlerFunc {
|
||||||
return app.authenticate
|
return app.authenticate
|
||||||
}
|
}
|
||||||
@ -133,7 +152,7 @@ type getTokenDTO struct {
|
|||||||
Token string `json:"token" example:"kjsdklsfdkljfsjsdfklsdfkldsfjdfskjsdfjklsdf"` // API token for use with everything else.
|
Token string `json:"token" example:"kjsdklsfdkljfsjsdfklsdfkldsfjdfskjsdfjklsdf"` // API token for use with everything else.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) decodeValidateLoginHeader(gc *gin.Context) (username, password string, ok bool) {
|
func (app *appContext) decodeValidateLoginHeader(gc *gin.Context, userpage bool) (username, password string, ok bool) {
|
||||||
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
||||||
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
||||||
creds := strings.SplitN(string(auth), ":", 2)
|
creds := strings.SplitN(string(auth), ":", 2)
|
||||||
@ -141,7 +160,7 @@ func (app *appContext) decodeValidateLoginHeader(gc *gin.Context) (username, pas
|
|||||||
password = creds[1]
|
password = creds[1]
|
||||||
ok = false
|
ok = false
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
app.debug.Println("Auth denied: blank username/password")
|
app.logIpDebug(gc, userpage, "Auth denied: blank username/password")
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -149,17 +168,17 @@ func (app *appContext) decodeValidateLoginHeader(gc *gin.Context) (username, pas
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) validateJellyfinCredentials(username, password string, gc *gin.Context) (user mediabrowser.User, ok bool) {
|
func (app *appContext) validateJellyfinCredentials(username, password string, gc *gin.Context, userpage bool) (user mediabrowser.User, ok bool) {
|
||||||
ok = false
|
ok = false
|
||||||
user, status, err := app.authJf.Authenticate(username, password)
|
user, status, err := app.authJf.Authenticate(username, password)
|
||||||
if status != 200 || err != nil {
|
if status != 200 || err != nil {
|
||||||
if status == 401 || status == 400 {
|
if status == 401 || status == 400 {
|
||||||
app.info.Println("Auth denied: Invalid username/password (Jellyfin)")
|
app.logIpInfo(gc, userpage, "Auth denied: Invalid username/password (Jellyfin)")
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status == 403 {
|
if status == 403 {
|
||||||
app.info.Println("Auth denied: Jellyfin account disabled")
|
app.logIpInfo(gc, userpage, "Auth denied: Jellyfin account disabled")
|
||||||
respond(403, "yourAccountWasDisabled", gc)
|
respond(403, "yourAccountWasDisabled", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -180,8 +199,8 @@ func (app *appContext) validateJellyfinCredentials(username, password string, gc
|
|||||||
// @tags Auth
|
// @tags Auth
|
||||||
// @Security getTokenAuth
|
// @Security getTokenAuth
|
||||||
func (app *appContext) getTokenLogin(gc *gin.Context) {
|
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)
|
username, password, ok := app.decodeValidateLoginHeader(gc, false)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -196,12 +215,12 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !app.jellyfinLogin && !match {
|
if !app.jellyfinLogin && !match {
|
||||||
app.info.Println("Auth denied: Invalid username/password")
|
app.logIpInfo(gc, false, "Auth denied: Invalid username/password")
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !match {
|
if !match {
|
||||||
user, ok := app.validateJellyfinCredentials(username, password, gc)
|
user, ok := app.validateJellyfinCredentials(username, password, gc, false)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -285,7 +304,7 @@ func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName s
|
|||||||
// @Router /token/refresh [get]
|
// @Router /token/refresh [get]
|
||||||
// @tags Auth
|
// @tags Auth
|
||||||
func (app *appContext) getTokenRefresh(gc *gin.Context) {
|
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")
|
claims, ok := app.decodeValidateRefreshCookie(gc, "refresh")
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -120,6 +120,9 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
||||||
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
|
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
|
||||||
|
|
||||||
|
LOGIP = app.config.Section("advanced").Key("log_ips").MustBool(false)
|
||||||
|
LOGIPU = app.config.Section("advanced").Key("log_ips_userpage").MustBool(false)
|
||||||
|
|
||||||
// These two settings are pretty much the same
|
// These two settings are pretty much the same
|
||||||
url1 := app.config.Section("invite_emails").Key("url_base").String()
|
url1 := app.config.Section("invite_emails").Key("url_base").String()
|
||||||
url2 := app.config.Section("password_resets").Key("url_base").String()
|
url2 := app.config.Section("password_resets").Key("url_base").String()
|
||||||
|
@ -297,6 +297,29 @@
|
|||||||
"advanced": true
|
"advanced": true
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"log_ips": {
|
||||||
|
"name": "Log IPs accessing Admin Page",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Log IP addresses of admins and admin page requests in console and in activities. See notice below on legality."
|
||||||
|
},
|
||||||
|
"log_ips_users": {
|
||||||
|
"name": "Log IPs accessing User Page",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Log IP addresses of users in console and in activities. See notice below on legality."
|
||||||
|
},
|
||||||
|
"ip_note": {
|
||||||
|
"name": "Logging IPs:",
|
||||||
|
"type": "note",
|
||||||
|
"value": "",
|
||||||
|
"required": "false",
|
||||||
|
"description": "Logging IP addresses through jfa-go may violate GDPR or other privacy regulations, as IPs are linked to account information. Enable at your own risk."
|
||||||
|
},
|
||||||
"tls": {
|
"tls": {
|
||||||
"name": "TLS/HTTP2",
|
"name": "TLS/HTTP2",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
@ -554,48 +554,48 @@
|
|||||||
<div class="card @low dark:~d_neutral">
|
<div class="card @low dark:~d_neutral">
|
||||||
<span class="heading">{{ .strings.create }}</span>
|
<span class="heading">{{ .strings.create }}</span>
|
||||||
<div class="flex flex-col md:flex-row gap-3" id="create-inv">
|
<div class="flex flex-col md:flex-row gap-3" id="create-inv">
|
||||||
<div class="card ~neutral @low col">
|
<div class="card ~neutral @low flex flex-col gap-2 w-1/2">
|
||||||
<div class="row mb-2">
|
<div class="flex flex-row gap-2">
|
||||||
<label class="col mr-2">
|
<label class="w-1/2">
|
||||||
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
|
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
|
||||||
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
|
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="col ml-2">
|
<label class="w-1/2">
|
||||||
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
|
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
|
||||||
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
|
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="inv-duration">
|
<div id="inv-duration" class="flex flex-col gap-2">
|
||||||
<div class="row">
|
<div class="flex flex-row gap-2">
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="create-months">{{ .strings.inviteMonths }}</label>
|
<label class="label supra" for="create-months">{{ .strings.inviteMonths }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="create-months">
|
<select id="create-months">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
|
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="create-days">
|
<select id="create-days">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flex flex-row gap-2">
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
|
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="create-hours">
|
<select id="create-hours">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
|
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="create-minutes">
|
<select id="create-minutes">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
@ -603,44 +603,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="user-expiry" class="unfocused">
|
<div id="user-expiry" class="unfocused flex flex-col gap-2">
|
||||||
<p class="support mb-2">{{ .strings.userExpiryDescription }}</p>
|
<div class="flex flex-row gap-2">
|
||||||
<div class="mb-2">
|
<p class="support">{{ .strings.userExpiryDescription }}</p>
|
||||||
|
<div>
|
||||||
<label for="create-user-expiry-enabled" class="button ~neutral @low">
|
<label for="create-user-expiry-enabled" class="button ~neutral @low">
|
||||||
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
|
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
|
||||||
<span class="ml-2">{{ .strings.enabled }} </span>
|
<span class="ml-2">{{ .strings.enabled }} </span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col">
|
<div class="flex flex-row gap-2">
|
||||||
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="user-months">{{ .strings.inviteMonths }}</label>
|
<label class="label supra" for="user-months">{{ .strings.inviteMonths }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="user-months">
|
<select id="user-months">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="user-days">{{ .strings.inviteDays }}</label>
|
<label class="label supra" for="user-days">{{ .strings.inviteDays }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="user-days">
|
<select id="user-days">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flex flex-row gap-2">
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="user-hours">{{ .strings.inviteHours }}</label>
|
<label class="label supra" for="user-hours">{{ .strings.inviteHours }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="user-hours">
|
<select id="user-hours">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="grow flex flex-col gap-4">
|
||||||
<label class="label supra" for="user-minutes">{{ .strings.inviteMinutes }}</label>
|
<label class="label supra" for="user-minutes">{{ .strings.inviteMinutes }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="user-minutes">
|
<select id="user-minutes">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
@ -648,52 +650,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="flex flex-col gap-4">
|
||||||
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
|
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
|
||||||
<input type="text" id="create-label" class="input ~neutral @low mb-2 mt-4">
|
<input type="text" id="create-label" class="input ~neutral @low">
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
<label class="label supra" for="create-user-label"> {{ .strings.userLabel }}</label>
|
<label class="label supra" for="create-user-label"> {{ .strings.userLabel }}</label>
|
||||||
<p class="support">{{ .strings.userLabelDescription }}</p>
|
<p class="support">{{ .strings.userLabelDescription }}</p>
|
||||||
<input type="text" id="create-user-label" class="input ~neutral @low mb-2 mt-4">
|
</div>
|
||||||
|
<input type="text" id="create-user-label" class="input ~neutral @low">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral @low col">
|
<div class="card ~neutral @low flex flex-col justify-between gap-2 w-1/2">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
|
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
|
||||||
<div class="flex-expand mb-2 mt-4">
|
<div class="flex flex-row gap-2">
|
||||||
<input type="number" min="0" id="create-uses" class="input ~neutral @low mr-2" value=1>
|
<input type="number" min="0" id="create-uses" class="input ~neutral @low" value=1>
|
||||||
<label for="create-inf-uses" class="button ~neutral @low" title="Set uses to infinite">
|
<label for="create-inf-uses" class="button ~neutral @low" title="Set uses to infinite">
|
||||||
<span>∞</span>
|
<span>∞</span>
|
||||||
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
|
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="support unfocused my-2" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
|
</div>
|
||||||
|
<p class="support unfocused" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
<label class="label supra">{{ .strings.profile }}</label>
|
<label class="label supra">{{ .strings.profile }}</label>
|
||||||
<div class="select ~neutral @low mb-2 mt-4">
|
<div class="select ~neutral @low">
|
||||||
<select id="create-profile">
|
<select id="create-profile">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="create-send-to-container">
|
</div>
|
||||||
|
<div id="create-send-to-container" class="flex flex-col gap-4">
|
||||||
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
||||||
<div class="flex-expand mb-2 mt-4">
|
<div class="flex flex-row gap-2">
|
||||||
{{ if .discordEnabled }}
|
{{ if .discordEnabled }}
|
||||||
<input type="text" id="create-send-to" class="input ~neutral @low mr-2" placeholder="example@example.com | user#1234">
|
<input type="text" id="create-send-to" class="input ~neutral @low" placeholder="example@example.com | user#1234">
|
||||||
<span id="create-send-to-search" class="button ~neutral @low mr-2">
|
<span id="create-send-to-search" class="button ~neutral @low">
|
||||||
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
|
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
|
||||||
</span>
|
</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<input type="email" id="create-send-to" class="input ~neutral @low mr-2" placeholder="example@example.com">
|
<input type="email" id="create-send-to" class="input ~neutral @low" placeholder="example@example.com">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<label for="create-send-to-enabled" class="button ~neutral @low">
|
<label for="create-send-to-enabled" class="button ~neutral @low">
|
||||||
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<span class="button ~urge @low supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
|
<span class="button ~urge @low supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="tab-accounts" class="unfocused">
|
<div id="tab-accounts" class="unfocused">
|
||||||
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible">
|
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible">
|
||||||
<div id="accounts-filter-dropdown" class="dropdown manual z-10 w-100">
|
<div id="accounts-filter-dropdown" class="dropdown manual z-10 w-100">
|
||||||
|
@ -150,7 +150,8 @@
|
|||||||
"accountDisabled": "Account disabled: {user}",
|
"accountDisabled": "Account disabled: {user}",
|
||||||
"accountReEnabled": "Account re-enabled: {user}",
|
"accountReEnabled": "Account re-enabled: {user}",
|
||||||
"accountExpired": "Account expired: {user}",
|
"accountExpired": "Account expired: {user}",
|
||||||
"accountWillExpire": "Account will expire on {date}",
|
"accountWillExpire": "Account will expire on {date}.",
|
||||||
|
"expirationBasedOn": "Given date based on 1st user.",
|
||||||
"userDeleted": "User was deleted.",
|
"userDeleted": "User was deleted.",
|
||||||
"userDisabled": "User was disabled",
|
"userDisabled": "User was disabled",
|
||||||
"inviteCreated": "Invite created: {invite}",
|
"inviteCreated": "Invite created: {invite}",
|
||||||
|
2
main.go
2
main.go
@ -46,6 +46,8 @@ var (
|
|||||||
SWAGGER *bool
|
SWAGGER *bool
|
||||||
QUIT = false
|
QUIT = false
|
||||||
RUNNING = false
|
RUNNING = false
|
||||||
|
LOGIP = false // Log admin IPs
|
||||||
|
LOGIPU = false // Log user IPs
|
||||||
// Used to know how many times to re-broadcast restart signal.
|
// Used to know how many times to re-broadcast restart signal.
|
||||||
RESTARTLISTENERCOUNT = 0
|
RESTARTLISTENERCOUNT = 0
|
||||||
warning = color.New(color.FgYellow).SprintfFunc()
|
warning = color.New(color.FgYellow).SprintfFunc()
|
||||||
|
@ -445,6 +445,7 @@ type ActivityDTO struct {
|
|||||||
InviteCode string `json:"invite_code"`
|
InviteCode string `json:"invite_code"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Time int64 `json:"time"`
|
Time int64 `json:"time"`
|
||||||
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetActivitiesDTO struct {
|
type GetActivitiesDTO struct {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/hrfee/jfa-go/logger"
|
"github.com/hrfee/jfa-go/logger"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/timshannon/badgerhold/v4"
|
"github.com/timshannon/badgerhold/v4"
|
||||||
@ -55,6 +56,7 @@ type Activity struct {
|
|||||||
InviteCode string // Set for ActivityCreation, create/deleteInvite
|
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.
|
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
|
Time time.Time
|
||||||
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserExpiry struct {
|
type UserExpiry struct {
|
||||||
@ -563,8 +565,12 @@ func (st *Storage) GetActivityKey(k string) (Activity, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetActivityKey stores value v in key k.
|
// 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
|
v.ID = k
|
||||||
|
if gc != nil && ((LOGIPU && user) || (LOGIP && !user)) {
|
||||||
|
v.IP = gc.ClientIP()
|
||||||
|
}
|
||||||
err := st.db.Upsert(k, v)
|
err := st.db.Upsert(k, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fmt.Printf("Failed to set custom content: %v\n", err)
|
// fmt.Printf("Failed to set custom content: %v\n", err)
|
||||||
|
@ -1634,6 +1634,7 @@ export class accountsList {
|
|||||||
_displayExpiryDate = () => {
|
_displayExpiryDate = () => {
|
||||||
let date: Date;
|
let date: Date;
|
||||||
let invalid = false;
|
let invalid = false;
|
||||||
|
let users = this._collectUsers();
|
||||||
if (this._usingExtendExpiryTextInput) {
|
if (this._usingExtendExpiryTextInput) {
|
||||||
date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date;
|
date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date;
|
||||||
invalid = "invalid" in (date as any);
|
invalid = "invalid" in (date as any);
|
||||||
@ -1645,7 +1646,7 @@ export class accountsList {
|
|||||||
document.getElementById("extend-expiry-minutes") as HTMLSelectElement
|
document.getElementById("extend-expiry-minutes") as HTMLSelectElement
|
||||||
];
|
];
|
||||||
invalid = fields[0].value == "0" && fields[1].value == "0" && fields[2].value == "0" && fields[3].value == "0";
|
invalid = fields[0].value == "0" && fields[1].value == "0" && fields[2].value == "0" && fields[3].value == "0";
|
||||||
let id = this._collectUsers().length == 1 ? this._collectUsers()[0] : "";
|
let id = users.length > 0 ? users[0] : "";
|
||||||
if (!id) invalid = true;
|
if (!id) invalid = true;
|
||||||
else {
|
else {
|
||||||
date = new Date(this._users[id].expiry*1000);
|
date = new Date(this._users[id].expiry*1000);
|
||||||
@ -1665,7 +1666,12 @@ export class accountsList {
|
|||||||
} else {
|
} else {
|
||||||
submit.disabled = false;
|
submit.disabled = false;
|
||||||
submitSpan.classList.remove("opacity-60");
|
submitSpan.classList.remove("opacity-60");
|
||||||
this._extendExpiryDate.textContent = window.lang.strings("accountWillExpire").replace("{date}", toDateString(date));
|
this._extendExpiryDate.innerHTML = `
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span>${window.lang.strings("accountWillExpire").replace("{date}", toDateString(date))}</span>
|
||||||
|
${users.length > 1 ? "<span>"+window.lang.strings("expirationBasedOn")+"</span>" : ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
this._extendExpiryDate.classList.remove("unfocused");
|
this._extendExpiryDate.classList.remove("unfocused");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1740,6 +1746,9 @@ export class accountsList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._extendExpiryTextInput.value = "";
|
this._extendExpiryTextInput.value = "";
|
||||||
|
this._usingExtendExpiryTextInput = false;
|
||||||
|
this._extendExpiryDate.classList.add("unfocused");
|
||||||
|
this._displayExpiryDate();
|
||||||
window.modals.extendExpiry.show();
|
window.modals.extendExpiry.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export interface activity {
|
|||||||
time: number;
|
time: number;
|
||||||
username: string;
|
username: string;
|
||||||
source_username: string;
|
source_username: string;
|
||||||
|
ip: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityTypeMoods = {
|
var activityTypeMoods = {
|
||||||
@ -43,6 +44,7 @@ export class Activity implements activity, SearchableItem {
|
|||||||
private _referrer: HTMLElement;
|
private _referrer: HTMLElement;
|
||||||
private _expiryTypeBadge: HTMLElement;
|
private _expiryTypeBadge: HTMLElement;
|
||||||
private _delete: HTMLElement;
|
private _delete: HTMLElement;
|
||||||
|
private _ip: HTMLElement;
|
||||||
private _act: activity;
|
private _act: activity;
|
||||||
private _urlBase: string = ((): string => {
|
private _urlBase: string = ((): string => {
|
||||||
let link = window.location.href;
|
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 = `<span class="supra mr-2">IP</span><span class="font-mono bg-inherit">${v}</span>`;
|
||||||
|
} else {
|
||||||
|
this._ip.textContent = ``;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get invite_code(): string { return this._act.invite_code; }
|
get invite_code(): string { return this._act.invite_code; }
|
||||||
set invite_code(v: string) {
|
set invite_code(v: string) {
|
||||||
this._act.invite_code = v;
|
this._act.invite_code = v;
|
||||||
@ -260,12 +272,13 @@ export class Activity implements activity, SearchableItem {
|
|||||||
<span class="activity-expiry-type badge self-start md:self-end mt-1"></span>
|
<span class="activity-expiry-type badge self-start md:self-end mt-1"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row justify-between">
|
<div class="flex flex-row justify-between items-end">
|
||||||
|
<div class="flex flex-col md:flex-row gap-2">
|
||||||
<div>
|
<div>
|
||||||
<span class="content supra mr-2 activity-source-type"></span><span class="activity-source"></span>
|
<span class="content supra mr-2 activity-source-type"></span><span class="activity-source"></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<span class="content activity-referrer"></span>
|
<span class="content activity-referrer"></span>
|
||||||
|
<span class="content activity-ip"></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="button @low hover:~critical rounded-full px-1 py-px activity-delete" aria-label="${window.lang.strings("delete")}"><i class="ri-close-line"></i></button>
|
<button class="button @low hover:~critical rounded-full px-1 py-px activity-delete" aria-label="${window.lang.strings("delete")}"><i class="ri-close-line"></i></button>
|
||||||
@ -277,6 +290,7 @@ export class Activity implements activity, SearchableItem {
|
|||||||
this._time = this._card.querySelector(".activity-time");
|
this._time = this._card.querySelector(".activity-time");
|
||||||
this._sourceType = this._card.querySelector(".activity-source-type");
|
this._sourceType = this._card.querySelector(".activity-source-type");
|
||||||
this._source = this._card.querySelector(".activity-source");
|
this._source = this._card.querySelector(".activity-source");
|
||||||
|
this._ip = this._card.querySelector(".activity-ip");
|
||||||
this._referrer = this._card.querySelector(".activity-referrer");
|
this._referrer = this._card.querySelector(".activity-referrer");
|
||||||
this._expiryTypeBadge = this._card.querySelector(".activity-expiry-type");
|
this._expiryTypeBadge = this._card.querySelector(".activity-expiry-type");
|
||||||
this._delete = this._card.querySelector(".activity-delete");
|
this._delete = this._card.querySelector(".activity-delete");
|
||||||
@ -324,6 +338,7 @@ export class Activity implements activity, SearchableItem {
|
|||||||
this.source = act.source;
|
this.source = act.source;
|
||||||
this.value = act.value;
|
this.value = act.value;
|
||||||
this.type = act.type;
|
this.type = act.type;
|
||||||
|
this.ip = act.ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete = () => _delete("/activity/" + this._act.id, null, (req: XMLHttpRequest) => {
|
delete = () => _delete("/activity/" + this._act.id, null, (req: XMLHttpRequest) => {
|
||||||
|
@ -45,13 +45,13 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
|||||||
respond(500, "Contact Admin", gc)
|
respond(500, "Contact Admin", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.info.Println("UserToken requested (login attempt)")
|
app.logIpInfo(gc, true, "UserToken requested (login attempt)")
|
||||||
username, password, ok := app.decodeValidateLoginHeader(gc)
|
username, password, ok := app.decodeValidateLoginHeader(gc, true)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, ok := app.validateJellyfinCredentials(username, password, gc)
|
user, ok := app.validateJellyfinCredentials(username, password, gc, true)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app.info.Println("UserToken request (refresh token)")
|
app.logIpInfo(gc, true, "UserToken request (refresh token)")
|
||||||
claims, ok := app.decodeValidateRefreshCookie(gc, "user-refresh")
|
claims, ok := app.decodeValidateRefreshCookie(gc, "user-refresh")
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -120,7 +120,7 @@ func (app *appContext) checkUsers() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
app.storage.SetActivityKey(shortuuid.New(), activity)
|
app.storage.SetActivityKey(shortuuid.New(), activity, nil, false)
|
||||||
|
|
||||||
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
||||||
app.jf.CacheExpiry = time.Now()
|
app.jf.CacheExpiry = time.Now()
|
||||||
|
4
views.go
4
views.go
@ -365,7 +365,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
SourceType: ActivityUser,
|
SourceType: ActivityUser,
|
||||||
Source: jfUser.ID,
|
Source: jfUser.ID,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
}, gc, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,7 +655,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
app.debug.Printf("Invalid key")
|
app.debug.Printf("Invalid key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, success := app.newUser(req, true)
|
f, success := app.newUser(req, true, gc)
|
||||||
if !success {
|
if !success {
|
||||||
app.err.Printf("Failed to create new user")
|
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.
|
// Not meant for us. Calling this will be a mess, but at least it might give us some information.
|
||||||
|
Loading…
Reference in New Issue
Block a user