mirror of
https://github.com/hrfee/jfa-go.git
synced 2025-01-21 07:40:11 +00:00
implement user expiry functionality
All works now, but i'll add a field on the accounts tab for users with an expiry, as well as a 'disabled' badge.
This commit is contained in:
parent
2934832a98
commit
1e9d184508
82
api.go
82
api.go
@ -445,6 +445,14 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
|||||||
app.info.Printf("%s: Sent welcome email to %s", req.Username, req.Email)
|
app.info.Printf("%s: Sent welcome email to %s", req.Username, req.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if invite.UserExpiry {
|
||||||
|
expiry := time.Now().Add(time.Duration(60*(invite.UserDays*24+invite.UserHours)+invite.UserMinutes) * time.Minute)
|
||||||
|
app.storage.users[id] = expiry
|
||||||
|
err := app.storage.storeUsers()
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to store user duration: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
success = true
|
success = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -634,8 +642,8 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
invite.RemainingUses = 1
|
invite.RemainingUses = 1
|
||||||
}
|
}
|
||||||
invite.UserDuration = req.UserDuration
|
invite.UserExpiry = req.UserExpiry
|
||||||
if invite.UserDuration {
|
if invite.UserExpiry {
|
||||||
invite.UserDays = req.UserDays
|
invite.UserDays = req.UserDays
|
||||||
invite.UserHours = req.UserHours
|
invite.UserHours = req.UserHours
|
||||||
invite.UserMinutes = req.UserMinutes
|
invite.UserMinutes = req.UserMinutes
|
||||||
@ -819,18 +827,18 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
for code, inv := range app.storage.invites {
|
for code, inv := range app.storage.invites {
|
||||||
_, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
_, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||||
invite := inviteDTO{
|
invite := inviteDTO{
|
||||||
Code: code,
|
Code: code,
|
||||||
Days: days,
|
Days: days,
|
||||||
Hours: hours,
|
Hours: hours,
|
||||||
Minutes: minutes,
|
Minutes: minutes,
|
||||||
UserDuration: inv.UserDuration,
|
UserExpiry: inv.UserExpiry,
|
||||||
UserDays: inv.UserDays,
|
UserDays: inv.UserDays,
|
||||||
UserHours: inv.UserHours,
|
UserHours: inv.UserHours,
|
||||||
UserMinutes: inv.UserMinutes,
|
UserMinutes: inv.UserMinutes,
|
||||||
Created: app.formatDatetime(inv.Created),
|
Created: app.formatDatetime(inv.Created),
|
||||||
Profile: inv.Profile,
|
Profile: inv.Profile,
|
||||||
NoLimit: inv.NoLimit,
|
NoLimit: inv.NoLimit,
|
||||||
Label: inv.Label,
|
Label: inv.Label,
|
||||||
}
|
}
|
||||||
if len(inv.UsedBy) != 0 {
|
if len(inv.UsedBy) != 0 {
|
||||||
invite.UsedBy = inv.UsedBy
|
invite.UsedBy = inv.UsedBy
|
||||||
@ -1281,18 +1289,24 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
|
|||||||
|
|
||||||
// @Summary Get a list of email names and IDs.
|
// @Summary Get a list of email names and IDs.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Param lang query string false "Language for email titles."
|
||||||
// @Success 200 {object} emailListDTO
|
// @Success 200 {object} emailListDTO
|
||||||
// @Router /config/emails [get]
|
// @Router /config/emails [get]
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) GetEmails(gc *gin.Context) {
|
func (app *appContext) GetEmails(gc *gin.Context) {
|
||||||
|
lang := gc.Query("lang")
|
||||||
|
if _, ok := app.storage.lang.Email[lang]; !ok {
|
||||||
|
lang = app.storage.lang.chosenEmailLang
|
||||||
|
}
|
||||||
gc.JSON(200, emailListDTO{
|
gc.JSON(200, emailListDTO{
|
||||||
"UserCreated": {Name: app.storage.lang.Email["en-us"].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
|
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
|
||||||
"InviteExpiry": {Name: app.storage.lang.Email["en-us"].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
|
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
|
||||||
"PasswordReset": {Name: app.storage.lang.Email["en-us"].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
|
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
|
||||||
"UserDeleted": {Name: app.storage.lang.Email["en-us"].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
|
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
|
||||||
"InviteEmail": {Name: app.storage.lang.Email["en-us"].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
|
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
|
||||||
"WelcomeEmail": {Name: app.storage.lang.Email["en-us"].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
|
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
|
||||||
"EmailConfirmation": {Name: app.storage.lang.Email["en-us"].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
|
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
|
||||||
|
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.customEmails.UserExpired.Enabled},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1333,6 +1347,9 @@ func (app *appContext) SetEmail(gc *gin.Context) {
|
|||||||
} else if id == "EmailConfirmation" {
|
} else if id == "EmailConfirmation" {
|
||||||
app.storage.customEmails.EmailConfirmation.Content = req.Content
|
app.storage.customEmails.EmailConfirmation.Content = req.Content
|
||||||
app.storage.customEmails.EmailConfirmation.Enabled = true
|
app.storage.customEmails.EmailConfirmation.Enabled = true
|
||||||
|
} else if id == "UserExpired" {
|
||||||
|
app.storage.customEmails.UserExpired.Content = req.Content
|
||||||
|
app.storage.customEmails.UserExpired.Enabled = true
|
||||||
} else {
|
} else {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
@ -1374,6 +1391,8 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
|
|||||||
app.storage.customEmails.WelcomeEmail.Enabled = enabled
|
app.storage.customEmails.WelcomeEmail.Enabled = enabled
|
||||||
} else if id == "EmailConfirmation" {
|
} else if id == "EmailConfirmation" {
|
||||||
app.storage.customEmails.EmailConfirmation.Enabled = enabled
|
app.storage.customEmails.EmailConfirmation.Enabled = enabled
|
||||||
|
} else if id == "UserExpired" {
|
||||||
|
app.storage.customEmails.UserExpired.Enabled = enabled
|
||||||
} else {
|
} else {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
@ -1393,6 +1412,7 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
|
|||||||
// @Router /config/emails/{id} [get]
|
// @Router /config/emails/{id} [get]
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) GetEmail(gc *gin.Context) {
|
func (app *appContext) GetEmail(gc *gin.Context) {
|
||||||
|
lang := app.storage.lang.chosenEmailLang
|
||||||
id := gc.Param("id")
|
id := gc.Param("id")
|
||||||
var content string
|
var content string
|
||||||
var err error
|
var err error
|
||||||
@ -1401,8 +1421,8 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
var values map[string]interface{}
|
var values map[string]interface{}
|
||||||
var writeVars func(variables []string)
|
var writeVars func(variables []string)
|
||||||
newEmail := false
|
newEmail := false
|
||||||
username := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("username")
|
username := app.storage.lang.Email[lang].Strings.get("username")
|
||||||
emailAddress := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("emailAddress")
|
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
||||||
if id == "UserCreated" {
|
if id == "UserCreated" {
|
||||||
content = app.storage.customEmails.UserCreated.Content
|
content = app.storage.customEmails.UserCreated.Content
|
||||||
if content == "" {
|
if content == "" {
|
||||||
@ -1449,7 +1469,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
variables = app.storage.customEmails.UserDeleted.Variables
|
variables = app.storage.customEmails.UserDeleted.Variables
|
||||||
}
|
}
|
||||||
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
|
||||||
values = app.email.deletedValues(app.storage.lang.Email[app.storage.lang.chosenEmailLang].UserDeleted.get("reason"), app, false)
|
values = app.email.deletedValues(app.storage.lang.Email[lang].UserDeleted.get("reason"), app, false)
|
||||||
// app.storage.customEmails.UserDeleted = content
|
// app.storage.customEmails.UserDeleted = content
|
||||||
} else if id == "InviteEmail" {
|
} else if id == "InviteEmail" {
|
||||||
content = app.storage.customEmails.InviteEmail.Content
|
content = app.storage.customEmails.InviteEmail.Content
|
||||||
@ -1487,6 +1507,17 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
writeVars = func(variables []string) { app.storage.customEmails.EmailConfirmation.Variables = variables }
|
writeVars = func(variables []string) { app.storage.customEmails.EmailConfirmation.Variables = variables }
|
||||||
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
|
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
|
||||||
// app.storage.customEmails.EmailConfirmation = content
|
// app.storage.customEmails.EmailConfirmation = content
|
||||||
|
} else if id == "UserExpired" {
|
||||||
|
content = app.storage.customEmails.UserExpired.Content
|
||||||
|
if content == "" {
|
||||||
|
newEmail = true
|
||||||
|
msg, err = app.email.constructUserExpired(app, true)
|
||||||
|
content = msg.Text
|
||||||
|
} else {
|
||||||
|
variables = app.storage.customEmails.UserExpired.Variables
|
||||||
|
}
|
||||||
|
writeVars = func(variables []string) { app.storage.customEmails.UserExpired.Variables = variables }
|
||||||
|
values = app.email.userExpiredValues(app, false)
|
||||||
} else {
|
} else {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
@ -1515,6 +1546,9 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
writeVars(variables)
|
writeVars(variables)
|
||||||
}
|
}
|
||||||
|
if variables == nil {
|
||||||
|
variables = []string{}
|
||||||
|
}
|
||||||
if app.storage.storeCustomEmails() != nil {
|
if app.storage.storeCustomEmails() != nil {
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
|
@ -36,7 +36,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails"} {
|
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users"} {
|
||||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
||||||
}
|
}
|
||||||
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
||||||
@ -66,6 +66,10 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.config.Section("template_email").Key("email_html").SetValue(app.config.Section("template_email").Key("email_html").MustString("jfa-go:" + "template.html"))
|
app.config.Section("template_email").Key("email_html").SetValue(app.config.Section("template_email").Key("email_html").MustString("jfa-go:" + "template.html"))
|
||||||
app.config.Section("template_email").Key("email_text").SetValue(app.config.Section("template_email").Key("email_text").MustString("jfa-go:" + "template.txt"))
|
app.config.Section("template_email").Key("email_text").SetValue(app.config.Section("template_email").Key("email_text").MustString("jfa-go:" + "template.txt"))
|
||||||
|
|
||||||
|
app.config.Section("user_expiry").Key("behaviour").SetValue(app.config.Section("user_expiry").Key("behaviour").MustString("disable_user"))
|
||||||
|
app.config.Section("user_expiry").Key("email_html").SetValue(app.config.Section("user_expiry").Key("email_html").MustString("jfa-go:" + "user-expired.html"))
|
||||||
|
app.config.Section("user_expiry").Key("email_text").SetValue(app.config.Section("user_expiry").Key("email_text").MustString("jfa-go:" + "user-expired.txt"))
|
||||||
|
|
||||||
app.config.Section("jellyfin").Key("version").SetValue(version)
|
app.config.Section("jellyfin").Key("version").SetValue(version)
|
||||||
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))
|
||||||
|
@ -765,6 +765,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"user_expiry": {
|
||||||
|
"order": [],
|
||||||
|
"meta": {
|
||||||
|
"name": "User Expiry",
|
||||||
|
"description": "When set on an invite, users will be deleted or disabled a specified amount of time after they create their account."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"behaviour": {
|
||||||
|
"name": "Behaviour",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "select",
|
||||||
|
"options": [
|
||||||
|
["delete_user", "Delete user"],
|
||||||
|
["disable_user", "Disable user"]
|
||||||
|
],
|
||||||
|
"value": "disable_user",
|
||||||
|
"description": "Whether to delete or disable users on expiry."
|
||||||
|
},
|
||||||
|
"send_email": {
|
||||||
|
"name": "Send email",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "bool",
|
||||||
|
"value": true,
|
||||||
|
"depends_true": "email|method",
|
||||||
|
"description": "Send an email when a user's account expires."
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"name": "Email subject",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "email|method",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Subject of user expiry emails."
|
||||||
|
},
|
||||||
|
"email_html": {
|
||||||
|
"name": "Custom email (HTML)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "email|method",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to custom email html"
|
||||||
|
},
|
||||||
|
"email_text": {
|
||||||
|
"name": "Custom email (plaintext)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "email|method",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to custom email in plain text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
@ -822,6 +879,14 @@
|
|||||||
"value": "",
|
"value": "",
|
||||||
"description": "Location of stored email addresses (json)."
|
"description": "Location of stored email addresses (json)."
|
||||||
},
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "User storage",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Stores users temporarily when a user expiry is set."
|
||||||
|
},
|
||||||
"ombi_template": {
|
"ombi_template": {
|
||||||
"name": "Ombi user template",
|
"name": "Ombi user template",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
10
daemon.go
10
daemon.go
@ -4,7 +4,7 @@ import "time"
|
|||||||
|
|
||||||
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
|
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
|
||||||
|
|
||||||
type repeater struct {
|
type inviteDaemon struct {
|
||||||
Stopped bool
|
Stopped bool
|
||||||
ShutdownChannel chan string
|
ShutdownChannel chan string
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
@ -12,8 +12,8 @@ type repeater struct {
|
|||||||
app *appContext
|
app *appContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRepeater(interval time.Duration, app *appContext) *repeater {
|
func newInviteDaemon(interval time.Duration, app *appContext) *inviteDaemon {
|
||||||
return &repeater{
|
return &inviteDaemon{
|
||||||
Stopped: false,
|
Stopped: false,
|
||||||
ShutdownChannel: make(chan string),
|
ShutdownChannel: make(chan string),
|
||||||
Interval: interval,
|
Interval: interval,
|
||||||
@ -22,7 +22,7 @@ func newRepeater(interval time.Duration, app *appContext) *repeater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *repeater) run() {
|
func (rt *inviteDaemon) run() {
|
||||||
rt.app.info.Println("Invite daemon started")
|
rt.app.info.Println("Invite daemon started")
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -42,7 +42,7 @@ func (rt *repeater) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *repeater) shutdown() {
|
func (rt *inviteDaemon) shutdown() {
|
||||||
rt.Stopped = true
|
rt.Stopped = true
|
||||||
rt.ShutdownChannel <- "Down"
|
rt.ShutdownChannel <- "Down"
|
||||||
<-rt.ShutdownChannel
|
<-rt.ShutdownChannel
|
||||||
|
36
email.go
36
email.go
@ -569,6 +569,42 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) userExpiredValues(app *appContext, noSub bool) map[string]interface{} {
|
||||||
|
template := map[string]interface{}{
|
||||||
|
"yourAccountHasExpired": emailer.lang.UserExpired.get("yourAccountHasExpired"),
|
||||||
|
"contactTheAdmin": emailer.lang.UserExpired.get("contactTheAdmin"),
|
||||||
|
"message": "",
|
||||||
|
}
|
||||||
|
if !noSub {
|
||||||
|
template["message"] = app.config.Section("email").Key("message").String()
|
||||||
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Email, error) {
|
||||||
|
email := &Email{
|
||||||
|
Subject: app.config.Section("user_expiry").Key("subject").MustString(emailer.lang.UserExpired.get("title")),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
template := emailer.userExpiredValues(app, noSub)
|
||||||
|
if app.storage.customEmails.UserExpired.Enabled {
|
||||||
|
content := app.storage.customEmails.UserExpired.Content
|
||||||
|
for _, v := range app.storage.customEmails.UserExpired.Variables {
|
||||||
|
replaceWith, ok := template[v[1:len(v)-1]]
|
||||||
|
if ok {
|
||||||
|
content = strings.ReplaceAll(content, v, replaceWith.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
email, err = emailer.constructTemplate(email.Subject, content, app)
|
||||||
|
} else {
|
||||||
|
email.HTML, email.Text, err = emailer.construct(app, "user_expiry", "email_", template)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return email, nil
|
||||||
|
}
|
||||||
|
|
||||||
// calls the send method in the underlying emailClient.
|
// calls the send method in the underlying emailClient.
|
||||||
func (emailer *Emailer) send(email *Email, address ...string) error {
|
func (emailer *Emailer) send(email *Email, address ...string) error {
|
||||||
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)
|
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
window.ombiEnabled = {{ .ombiEnabled }};
|
||||||
window.usernameEnabled = {{ .username }};
|
window.usernameEnabled = {{ .username }};
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
|
window.language = "{{ .langName }}";
|
||||||
</script>
|
</script>
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>Admin - jfa-go</title>
|
<title>Admin - jfa-go</title>
|
||||||
@ -132,7 +133,7 @@
|
|||||||
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col flex-col content mt-half">
|
<div class="col flex-col content mt-half">
|
||||||
<span class="label supra" for="editor-variables">{{ .strings.variables }}</span>
|
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
||||||
<div id="editor-variables"></div>
|
<div id="editor-variables"></div>
|
||||||
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
||||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral !normal mt-half monospace"></textarea>
|
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral !normal mt-half monospace"></textarea>
|
||||||
@ -265,8 +266,8 @@
|
|||||||
<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="flex-row-group ml-1">
|
<label class="flex-row-group ml-1">
|
||||||
<input type="radio" name="duration" class="unfocused" id="radio-user-duration">
|
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
|
||||||
<span class="button ~neutral !normal supra full-width center">{{ .strings.userDuration }}</span>
|
<span class="button ~neutral !normal supra full-width center">{{ .strings.userExpiry }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="inv-duration">
|
<div id="inv-duration">
|
||||||
@ -289,11 +290,11 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="user-duration" class="unfocused">
|
<div id="user-expiry" class="unfocused">
|
||||||
<p class="support">{{ .strings.userDurationDescription }}</p>
|
<p class="support">{{ .strings.userExpiryDescription }}</p>
|
||||||
<div class="mb-half">
|
<div class="mb-half">
|
||||||
<label for="create-user-duration-enabled" class="button ~neutral !normal">
|
<label for="create-user-expiry-enabled" class="button ~neutral !normal">
|
||||||
<input type="checkbox" id="create-user-duration-enabled" aria-label="User duration enabled">
|
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
|
||||||
<span class="ml-half">{{ .strings.enabled }} </span>
|
<span class="ml-half">{{ .strings.enabled }} </span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
window.code = "{{ .code }}";
|
window.code = "{{ .code }}";
|
||||||
window.messages = JSON.parse({{ .notifications }});
|
window.messages = JSON.parse({{ .notifications }});
|
||||||
window.confirmation = {{ .confirmation }};
|
window.confirmation = {{ .confirmation }};
|
||||||
window.userDurationEnabled = {{ .userDuration }};
|
window.userExpiryEnabled = {{ .userExpiry }};
|
||||||
window.userDurationDays = {{ .userDurationDays }};
|
window.userExpiryDays = {{ .userExpiryDays }};
|
||||||
window.userDurationHours = {{ .userDurationHours }};
|
window.userExpiryHours = {{ .userExpiryHours }};
|
||||||
window.userDurationMinutes = {{ .userDurationMinutes }};
|
window.userExpiryMinutes = {{ .userExpiryMinutes }};
|
||||||
window.userDurationMessage = {{ .userDurationMessage }};
|
window.userExpiryMessage = {{ .userExpiryMessage }};
|
||||||
</script>
|
</script>
|
||||||
<script src="js/form.js" type="module"></script>
|
<script src="js/form.js" type="module"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -37,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{ if .userDuration }}
|
{{ if .userExpiry }}
|
||||||
<aside class="col aside sm ~warning" id="user-duration-message"></aside>
|
<aside class="col aside sm ~warning" id="user-expiry-message"></aside>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<form class="card ~neutral !normal" id="form-create" href="">
|
<form class="card ~neutral !normal" id="form-create" href="">
|
||||||
<label class="label supra">
|
<label class="label supra">
|
||||||
|
1
lang.go
1
lang.go
@ -83,6 +83,7 @@ type emailLang struct {
|
|||||||
InviteEmail langSection `json:"inviteEmail"`
|
InviteEmail langSection `json:"inviteEmail"`
|
||||||
WelcomeEmail langSection `json:"welcomeEmail"`
|
WelcomeEmail langSection `json:"welcomeEmail"`
|
||||||
EmailConfirmation langSection `json:"emailConfirmation"`
|
EmailConfirmation langSection `json:"emailConfirmation"`
|
||||||
|
UserExpired langSection `json:"userExpired"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type setupLangs map[string]setupLang
|
type setupLangs map[string]setupLang
|
||||||
|
@ -22,11 +22,11 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"userDurationDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.",
|
|
||||||
"lastActiveTime": "Last Active",
|
"lastActiveTime": "Last Active",
|
||||||
"from": "From",
|
"from": "From",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"userDuration": "User Duration",
|
"userExpiry": "User Expiry",
|
||||||
|
"userExpiryDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.",
|
||||||
"aboutProgram": "About",
|
"aboutProgram": "About",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"commitNoun": "Commit",
|
"commitNoun": "Commit",
|
||||||
|
@ -55,5 +55,11 @@
|
|||||||
"title": "Confirm your email - Jellyfin",
|
"title": "Confirm your email - Jellyfin",
|
||||||
"clickBelow": "Click the link below to confirm your email address and start using Jellyfin.",
|
"clickBelow": "Click the link below to confirm your email address and start using Jellyfin.",
|
||||||
"confirmEmail": "Confirm Email"
|
"confirmEmail": "Confirm Email"
|
||||||
|
},
|
||||||
|
"userExpired": {
|
||||||
|
"name": "User expiry",
|
||||||
|
"title": "Your account has expired - Jellyfin",
|
||||||
|
"yourAccountHasExpired": "Your account has expired.",
|
||||||
|
"contactTheAdmin": "Contact the administrator for more info."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
mail/user-expired.mjml
Normal file
76
mail/user-expired.mjml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<mjml>
|
||||||
|
<mj-head>
|
||||||
|
<mj-raw>
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<meta name="supported-color-schemes" content="light dark">
|
||||||
|
</mj-raw>
|
||||||
|
<mj-style>
|
||||||
|
:root {
|
||||||
|
Color-scheme: light dark;
|
||||||
|
supported-color-schemes: light dark;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
Color-scheme: dark;
|
||||||
|
.body {
|
||||||
|
background: #242424 !important;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
}
|
||||||
|
[data-ogsc] .body {
|
||||||
|
background: #242424 !important;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
}
|
||||||
|
[data-ogsb] .body {
|
||||||
|
background: #242424 !important;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
Color-scheme: dark;
|
||||||
|
.body {
|
||||||
|
background: #242424 !important;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
}
|
||||||
|
[data-ogsc] .body {
|
||||||
|
background: #242424 !important;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
}
|
||||||
|
[data-ogsb] .body {
|
||||||
|
background: #242424 !important;
|
||||||
|
background-color: #242424 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</mj-style>
|
||||||
|
<mj-attributes>
|
||||||
|
<mj-class name="bg" background-color="#101010" />
|
||||||
|
<mj-class name="bg2" background-color="#242424" />
|
||||||
|
<mj-class name="text" color="#cacaca" />
|
||||||
|
<mj-class name="bold" color="rgba(255,255,255,0.87)" />
|
||||||
|
<mj-class name="secondary" color="rgb(153,153,153)" />
|
||||||
|
<mj-class name="blue" background-color="rgb(0,164,220)" />
|
||||||
|
</mj-attributes>
|
||||||
|
<mj-font name="Quicksand" href="https://fonts.googleapis.com/css2?family=Quicksand" />
|
||||||
|
<mj-font name="Noto Sans" href="https://fonts.googleapis.com/css2?family=Noto+Sans" />
|
||||||
|
</mj-head>
|
||||||
|
<mj-body>
|
||||||
|
<mj-section mj-class="bg2">
|
||||||
|
<mj-column>
|
||||||
|
<mj-text mj-class="bold" font-size="25px" font-family="Quicksand, Noto Sans, Helvetica, Arial, sans-serif"> {{ .jellyfin }} </mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
<mj-section mj-class="bg">
|
||||||
|
<mj-column>
|
||||||
|
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
|
||||||
|
<h3>{{ .yourAccountHasExpired }}</h3>
|
||||||
|
<p>{{ .contactTheAdmin }}</p>
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
<mj-section mj-class="bg2">
|
||||||
|
<mj-column>
|
||||||
|
<mj-text mj-class="secondary" font-style="italic" font-size="14px">
|
||||||
|
{{ .message }}
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</body>
|
||||||
|
</mjml>
|
5
mail/user-expired.txt
Normal file
5
mail/user-expired.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{{ .yourAccountHasExpired }}
|
||||||
|
|
||||||
|
{{ .contactTheAdmin }}
|
||||||
|
|
||||||
|
{{ .message }}
|
7
main.go
7
main.go
@ -338,6 +338,8 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.storage.loadConfiguration()
|
app.storage.loadConfiguration()
|
||||||
app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String()
|
app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String()
|
||||||
app.storage.loadDisplayprefs()
|
app.storage.loadDisplayprefs()
|
||||||
|
app.storage.users_path = app.config.Section("files").Key("users").String()
|
||||||
|
app.storage.loadUsers()
|
||||||
|
|
||||||
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
||||||
app.storage.loadProfiles()
|
app.storage.loadProfiles()
|
||||||
@ -510,9 +512,12 @@ func start(asDaemon, firstCall bool) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteDaemon := newRepeater(time.Duration(60*time.Second), app)
|
inviteDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
|
||||||
go inviteDaemon.run()
|
go inviteDaemon.run()
|
||||||
|
|
||||||
|
userDaemon := newUserDaemon(time.Duration(60*time.Second), app)
|
||||||
|
go userDaemon.run()
|
||||||
|
|
||||||
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
|
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
|
||||||
go app.StartPWR()
|
go app.StartPWR()
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ type generateInviteDTO struct {
|
|||||||
Days int `json:"days" example:"1"` // Number of days
|
Days int `json:"days" example:"1"` // Number of days
|
||||||
Hours int `json:"hours" example:"2"` // Number of hours
|
Hours int `json:"hours" example:"2"` // Number of hours
|
||||||
Minutes int `json:"minutes" example:"3"` // Number of minutes
|
Minutes int `json:"minutes" example:"3"` // Number of minutes
|
||||||
UserDuration bool `json:"user-duration"` // Whether or not user duration is enabled
|
UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
|
||||||
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
|
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
|
||||||
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
|
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
|
||||||
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
||||||
@ -76,7 +76,7 @@ type inviteDTO struct {
|
|||||||
Days int `json:"days" example:"1"` // Number of days till expiry
|
Days int `json:"days" example:"1"` // Number of days till expiry
|
||||||
Hours int `json:"hours" example:"2"` // Number of hours till expiry
|
Hours int `json:"hours" example:"2"` // Number of hours till expiry
|
||||||
Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
|
Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
|
||||||
UserDuration bool `json:"user-duration"` // Whether or not user duration is enabled
|
UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
|
||||||
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
|
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
|
||||||
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
|
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
|
||||||
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
||||||
@ -205,3 +205,7 @@ type customEmailDTO struct {
|
|||||||
HTML string `json:"html"`
|
HTML string `json:"html"`
|
||||||
Plaintext string `json:"plaintext"`
|
Plaintext string `json:"plaintext"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getEmailDTO struct {
|
||||||
|
Lang string `json:"lang" example:"en-us"` // Language code. If not given, defaults ot one specified in settings.
|
||||||
|
}
|
||||||
|
32
storage.go
32
storage.go
@ -14,16 +14,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
timePattern string
|
timePattern string
|
||||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path string
|
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path string
|
||||||
invites Invites
|
users map[string]time.Time
|
||||||
profiles map[string]Profile
|
invites Invites
|
||||||
defaultProfile string
|
profiles map[string]Profile
|
||||||
emails, displayprefs, ombi_template map[string]interface{}
|
defaultProfile string
|
||||||
customEmails customEmails
|
emails, displayprefs, ombi_template map[string]interface{}
|
||||||
policy mediabrowser.Policy
|
customEmails customEmails
|
||||||
configuration mediabrowser.Configuration
|
policy mediabrowser.Policy
|
||||||
lang Lang
|
configuration mediabrowser.Configuration
|
||||||
|
lang Lang
|
||||||
}
|
}
|
||||||
|
|
||||||
type customEmails struct {
|
type customEmails struct {
|
||||||
@ -34,6 +35,7 @@ type customEmails struct {
|
|||||||
InviteEmail customEmail `json:"inviteEmail"`
|
InviteEmail customEmail `json:"inviteEmail"`
|
||||||
WelcomeEmail customEmail `json:"welcomeEmail"`
|
WelcomeEmail customEmail `json:"welcomeEmail"`
|
||||||
EmailConfirmation customEmail `json:"emailConfirmation"`
|
EmailConfirmation customEmail `json:"emailConfirmation"`
|
||||||
|
UserExpired customEmail `json:"userExpired"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type customEmail struct {
|
type customEmail struct {
|
||||||
@ -59,7 +61,7 @@ type Invite struct {
|
|||||||
NoLimit bool `json:"no-limit"`
|
NoLimit bool `json:"no-limit"`
|
||||||
RemainingUses int `json:"remaining-uses"`
|
RemainingUses int `json:"remaining-uses"`
|
||||||
ValidTill time.Time `json:"valid_till"`
|
ValidTill time.Time `json:"valid_till"`
|
||||||
UserDuration bool `json:"user-duration"`
|
UserExpiry bool `json:"user-duration"`
|
||||||
UserDays int `json:"user-days,omitempty"`
|
UserDays int `json:"user-days,omitempty"`
|
||||||
UserHours int `json:"user-hours,omitempty"`
|
UserHours int `json:"user-hours,omitempty"`
|
||||||
UserMinutes int `json:"user-minutes,omitempty"`
|
UserMinutes int `json:"user-minutes,omitempty"`
|
||||||
@ -407,6 +409,14 @@ func (st *Storage) storeInvites() error {
|
|||||||
return storeJSON(st.invite_path, st.invites)
|
return storeJSON(st.invite_path, st.invites)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadUsers() error {
|
||||||
|
return loadJSON(st.users_path, &st.users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) storeUsers() error {
|
||||||
|
return storeJSON(st.users_path, st.users)
|
||||||
|
}
|
||||||
|
|
||||||
func (st *Storage) loadEmails() error {
|
func (st *Storage) loadEmails() error {
|
||||||
return loadJSON(st.emails_path, &st.emails)
|
return loadJSON(st.emails_path, &st.emails)
|
||||||
}
|
}
|
||||||
|
22
ts/form.ts
22
ts/form.ts
@ -10,11 +10,11 @@ interface formWindow extends Window {
|
|||||||
messages: { [key: string]: string };
|
messages: { [key: string]: string };
|
||||||
confirmation: boolean;
|
confirmation: boolean;
|
||||||
confirmationModal: Modal
|
confirmationModal: Modal
|
||||||
userDurationEnabled: boolean;
|
userExpiryEnabled: boolean;
|
||||||
userDurationDays: number;
|
userExpiryDays: number;
|
||||||
userDurationHours: number;
|
userExpiryHours: number;
|
||||||
userDurationMinutes: number;
|
userExpiryMinutes: number;
|
||||||
userDurationMessage: string;
|
userExpiryMessage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface pwValString {
|
interface pwValString {
|
||||||
@ -39,14 +39,14 @@ if (window.confirmation) {
|
|||||||
}
|
}
|
||||||
declare var window: formWindow;
|
declare var window: formWindow;
|
||||||
|
|
||||||
if (window.userDurationEnabled) {
|
if (window.userExpiryEnabled) {
|
||||||
const messageEl = document.getElementById("user-duration-message") as HTMLElement;
|
const messageEl = document.getElementById("user-expiry-message") as HTMLElement;
|
||||||
const calculateTime = () => {
|
const calculateTime = () => {
|
||||||
let time = new Date()
|
let time = new Date()
|
||||||
time.setDate(time.getDate() + window.userDurationDays);
|
time.setDate(time.getDate() + window.userExpiryDays);
|
||||||
time.setHours(time.getHours() + window.userDurationHours);
|
time.setHours(time.getHours() + window.userExpiryHours);
|
||||||
time.setMinutes(time.getMinutes() + window.userDurationMinutes);
|
time.setMinutes(time.getMinutes() + window.userExpiryMinutes);
|
||||||
messageEl.textContent = window.userDurationMessage.replace("{date}", time.toDateString() + " " + time.toLocaleTimeString());
|
messageEl.textContent = window.userExpiryMessage.replace("{date}", time.toDateString() + " " + time.toLocaleTimeString());
|
||||||
setTimeout(calculateTime, 1000);
|
setTimeout(calculateTime, 1000);
|
||||||
};
|
};
|
||||||
calculateTime();
|
calculateTime();
|
||||||
|
@ -59,20 +59,20 @@ export class DOMInvite implements Invite {
|
|||||||
get expiresIn(): string { return this._expiresIn }
|
get expiresIn(): string { return this._expiresIn }
|
||||||
set expiresIn(expiry: string) {
|
set expiresIn(expiry: string) {
|
||||||
this._expiresIn = expiry;
|
this._expiresIn = expiry;
|
||||||
this._infoArea.querySelector("span.inv-expiry").textContent = expiry;
|
this._infoArea.querySelector("span.inv-duration").textContent = expiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _userDuration: string;
|
private _userExpiry: string;
|
||||||
get userDurationTime(): string { return this._userDuration; }
|
get userExpiryTime(): string { return this._userExpiry; }
|
||||||
set userDurationTime(d: string) {
|
set userExpiryTime(d: string) {
|
||||||
const duration = this._middle.querySelector("span.user-duration") as HTMLSpanElement;
|
const expiry = this._middle.querySelector("span.user-expiry") as HTMLSpanElement;
|
||||||
if (!d) {
|
if (!d) {
|
||||||
duration.textContent = "";
|
expiry.textContent = "";
|
||||||
} else {
|
} else {
|
||||||
duration.textContent = window.lang.strings("userDuration");
|
expiry.textContent = window.lang.strings("userExpiry");
|
||||||
}
|
}
|
||||||
this._userDuration = d;
|
this._userExpiry = d;
|
||||||
this._middle.querySelector("strong.user-duration-time").textContent = d;
|
this._middle.querySelector("strong.user-expiry-time").textContent = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _remainingUses: string = "1";
|
private _remainingUses: string = "1";
|
||||||
@ -278,7 +278,7 @@ export class DOMInvite implements Invite {
|
|||||||
<span class="inv-email-chip"><i></i></span>
|
<span class="inv-email-chip"><i></i></span>
|
||||||
<span class="content sm"></span>
|
<span class="content sm"></span>
|
||||||
</div>
|
</div>
|
||||||
<span class="inv-expiry mr-1"></span>
|
<span class="inv-duration mr-1"></span>
|
||||||
<span class="button ~critical !normal inv-delete">${window.lang.strings("delete")}</span>
|
<span class="button ~critical !normal inv-delete">${window.lang.strings("delete")}</span>
|
||||||
<label>
|
<label>
|
||||||
<i class="icon clickable ri-arrow-down-s-line not-rotated"></i>
|
<i class="icon clickable ri-arrow-down-s-line not-rotated"></i>
|
||||||
@ -344,7 +344,7 @@ export class DOMInvite implements Invite {
|
|||||||
this._middle.innerHTML = `
|
this._middle.innerHTML = `
|
||||||
<p class="supra mb-1 top">${window.lang.strings("inviteDateCreated")} <strong class="inv-created"></strong></p>
|
<p class="supra mb-1 top">${window.lang.strings("inviteDateCreated")} <strong class="inv-created"></strong></p>
|
||||||
<p class="supra mb-1">${window.lang.strings("inviteRemainingUses")} <strong class="inv-remaining"></strong></p>
|
<p class="supra mb-1">${window.lang.strings("inviteRemainingUses")} <strong class="inv-remaining"></strong></p>
|
||||||
<p class="supra mb-1"><span class="user-duration"></span> <strong class="user-duration-time"></strong></p>
|
<p class="supra mb-1"><span class="user-expiry"></span> <strong class="user-expiry-time"></strong></p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this._right = document.createElement('div') as HTMLDivElement;
|
this._right = document.createElement('div') as HTMLDivElement;
|
||||||
@ -376,7 +376,7 @@ export class DOMInvite implements Invite {
|
|||||||
if (invite.label) {
|
if (invite.label) {
|
||||||
this.label = invite.label;
|
this.label = invite.label;
|
||||||
}
|
}
|
||||||
this.userDurationTime = invite.userDurationTime || "";
|
this.userExpiryTime = invite.userExpiryTime || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement = (): HTMLDivElement => { return this._container; }
|
asElement = (): HTMLDivElement => { return this._container; }
|
||||||
@ -477,16 +477,16 @@ function parseInvite(invite: { [f: string]: string | number | string[][] | boole
|
|||||||
parsed.email = invite["email"] as string || "";
|
parsed.email = invite["email"] as string || "";
|
||||||
parsed.label = invite["label"] as string || "";
|
parsed.label = invite["label"] as string || "";
|
||||||
let time = "";
|
let time = "";
|
||||||
let userDurationTime = "";
|
let userExpiryTime = "";
|
||||||
const fields = ["days", "hours", "minutes"];
|
const fields = ["days", "hours", "minutes"];
|
||||||
let prefixes = [""];
|
let prefixes = [""];
|
||||||
if (invite["user-duration"] as boolean) { prefixes.push("user-"); }
|
if (invite["user-expiry"] as boolean) { prefixes.push("user-"); }
|
||||||
for (let i = 0; i < fields.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
for (let j = 0; j < prefixes.length; j++) {
|
for (let j = 0; j < prefixes.length; j++) {
|
||||||
if (invite[prefixes[j]+fields[i]]) {
|
if (invite[prefixes[j]+fields[i]]) {
|
||||||
let text = `${invite[prefixes[j]+fields[i]]}${fields[i][0]} `;
|
let text = `${invite[prefixes[j]+fields[i]]}${fields[i][0]} `;
|
||||||
if (prefixes[j] == "user-") {
|
if (prefixes[j] == "user-") {
|
||||||
userDurationTime += text;
|
userExpiryTime += text;
|
||||||
} else {
|
} else {
|
||||||
time += text;
|
time += text;
|
||||||
}
|
}
|
||||||
@ -494,8 +494,8 @@ function parseInvite(invite: { [f: string]: string | number | string[][] | boole
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsed.expiresIn = window.lang.var("strings", "inviteExpiresInTime", time.slice(0, -1));
|
parsed.expiresIn = window.lang.var("strings", "inviteExpiresInTime", time.slice(0, -1));
|
||||||
parsed.userDuration = invite["user-duration"] as boolean;
|
parsed.userExpiry = invite["user-expiry"] as boolean;
|
||||||
parsed.userDurationTime = userDurationTime.slice(0, -1);
|
parsed.userExpiryTime = userExpiryTime.slice(0, -1);
|
||||||
parsed.remainingUses = invite["no-limit"] ? "∞" : String(invite["remaining-uses"])
|
parsed.remainingUses = invite["no-limit"] ? "∞" : String(invite["remaining-uses"])
|
||||||
parsed.usedBy = invite["used-by"] as string[][] || [];
|
parsed.usedBy = invite["used-by"] as string[][] || [];
|
||||||
parsed.created = invite["created"] as string || window.lang.strings("unknown");
|
parsed.created = invite["created"] as string || window.lang.strings("unknown");
|
||||||
@ -508,7 +508,7 @@ function parseInvite(invite: { [f: string]: string | number | string[][] | boole
|
|||||||
export class createInvite {
|
export class createInvite {
|
||||||
private _sendToEnabled = document.getElementById("create-send-to-enabled") as HTMLInputElement;
|
private _sendToEnabled = document.getElementById("create-send-to-enabled") as HTMLInputElement;
|
||||||
private _sendTo = document.getElementById("create-send-to") as HTMLInputElement;
|
private _sendTo = document.getElementById("create-send-to") as HTMLInputElement;
|
||||||
private _userDurationToggle = document.getElementById("create-user-duration-enabled") as HTMLInputElement;
|
private _userExpiryToggle = document.getElementById("create-user-expiry-enabled") as HTMLInputElement;
|
||||||
private _uses = document.getElementById('create-uses') as HTMLInputElement;
|
private _uses = document.getElementById('create-uses') as HTMLInputElement;
|
||||||
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
|
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
|
||||||
private _infUsesWarning = document.getElementById('create-inf-uses-warning') as HTMLParagraphElement;
|
private _infUsesWarning = document.getElementById('create-inf-uses-warning') as HTMLParagraphElement;
|
||||||
@ -524,9 +524,9 @@ export class createInvite {
|
|||||||
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
|
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
|
||||||
|
|
||||||
private _invDurationButton = document.getElementById('radio-inv-duration') as HTMLInputElement;
|
private _invDurationButton = document.getElementById('radio-inv-duration') as HTMLInputElement;
|
||||||
private _userDurationButton = document.getElementById('radio-user-duration') as HTMLInputElement;
|
private _userExpiryButton = document.getElementById('radio-user-expiry') as HTMLInputElement;
|
||||||
private _invDuration = document.getElementById('inv-duration');
|
private _invDuration = document.getElementById('inv-duration');
|
||||||
private _userDuration = document.getElementById('user-duration');
|
private _userExpiry = document.getElementById('user-expiry');
|
||||||
|
|
||||||
// Broadcast when new invite created
|
// Broadcast when new invite created
|
||||||
private _newInviteEvent = new CustomEvent("newInviteEvent");
|
private _newInviteEvent = new CustomEvent("newInviteEvent");
|
||||||
@ -619,11 +619,11 @@ export class createInvite {
|
|||||||
this._minutes.value = ""+n;
|
this._minutes.value = ""+n;
|
||||||
this._checkDurationValidity();
|
this._checkDurationValidity();
|
||||||
}
|
}
|
||||||
get userDuration(): boolean {
|
get userExpiry(): boolean {
|
||||||
return this._userDurationToggle.checked;
|
return this._userExpiryToggle.checked;
|
||||||
}
|
}
|
||||||
set userDuration(enabled: boolean) {
|
set userExpiry(enabled: boolean) {
|
||||||
this._userDurationToggle.checked = enabled;
|
this._userExpiryToggle.checked = enabled;
|
||||||
this._userDays.disabled = !enabled;
|
this._userDays.disabled = !enabled;
|
||||||
this._userHours.disabled = !enabled;
|
this._userHours.disabled = !enabled;
|
||||||
this._userMinutes.disabled = !enabled;
|
this._userMinutes.disabled = !enabled;
|
||||||
@ -679,15 +679,15 @@ export class createInvite {
|
|||||||
|
|
||||||
create = () => {
|
create = () => {
|
||||||
toggleLoader(this._createButton);
|
toggleLoader(this._createButton);
|
||||||
let userDuration = this.userDuration;
|
let userExpiry = this.userExpiry;
|
||||||
if (this.userDays == 0 && this.userHours == 0 && this.userMinutes == 0) {
|
if (this.userDays == 0 && this.userHours == 0 && this.userMinutes == 0) {
|
||||||
userDuration = false;
|
userExpiry = false;
|
||||||
}
|
}
|
||||||
let send = {
|
let send = {
|
||||||
"days": this.days,
|
"days": this.days,
|
||||||
"hours": this.hours,
|
"hours": this.hours,
|
||||||
"minutes": this.minutes,
|
"minutes": this.minutes,
|
||||||
"user-duration": userDuration,
|
"user-expiry": userExpiry,
|
||||||
"user-days": this.userDays,
|
"user-days": this.userDays,
|
||||||
"user-hours": this.userHours,
|
"user-hours": this.userHours,
|
||||||
"user-minutes": this.userMinutes,
|
"user-minutes": this.userMinutes,
|
||||||
@ -716,8 +716,8 @@ export class createInvite {
|
|||||||
this._infUses.onchange = () => { this.infiniteUses = this.infiniteUses; };
|
this._infUses.onchange = () => { this.infiniteUses = this.infiniteUses; };
|
||||||
this.infiniteUses = false;
|
this.infiniteUses = false;
|
||||||
this._sendToEnabled.onchange = () => { this.sendToEnabled = this.sendToEnabled; };
|
this._sendToEnabled.onchange = () => { this.sendToEnabled = this.sendToEnabled; };
|
||||||
this.userDuration = false;
|
this.userExpiry = false;
|
||||||
this._userDurationToggle.onchange = () => { this.userDuration = this._userDurationToggle.checked; }
|
this._userExpiryToggle.onchange = () => { this.userExpiry = this._userExpiryToggle.checked; }
|
||||||
this._userDays.disabled = true;
|
this._userDays.disabled = true;
|
||||||
this._userHours.disabled = true;
|
this._userHours.disabled = true;
|
||||||
this._userMinutes.disabled = true;
|
this._userMinutes.disabled = true;
|
||||||
@ -728,18 +728,17 @@ export class createInvite {
|
|||||||
this.label = "";
|
this.label = "";
|
||||||
|
|
||||||
const checkDuration = () => {
|
const checkDuration = () => {
|
||||||
console.log("bbbb")
|
|
||||||
const invSpan = this._invDurationButton.nextElementSibling as HTMLSpanElement;
|
const invSpan = this._invDurationButton.nextElementSibling as HTMLSpanElement;
|
||||||
const userSpan = this._userDurationButton.nextElementSibling as HTMLSpanElement;
|
const userSpan = this._userExpiryButton.nextElementSibling as HTMLSpanElement;
|
||||||
if (this._invDurationButton.checked) {
|
if (this._invDurationButton.checked) {
|
||||||
this._invDuration.classList.remove("unfocused");
|
this._invDuration.classList.remove("unfocused");
|
||||||
this._userDuration.classList.add("unfocused");
|
this._userExpiry.classList.add("unfocused");
|
||||||
invSpan.classList.add("!high");
|
invSpan.classList.add("!high");
|
||||||
invSpan.classList.remove("!normal");
|
invSpan.classList.remove("!normal");
|
||||||
userSpan.classList.add("!normal");
|
userSpan.classList.add("!normal");
|
||||||
userSpan.classList.remove("!high");
|
userSpan.classList.remove("!high");
|
||||||
} else if (this._userDurationButton.checked) {
|
} else if (this._userExpiryButton.checked) {
|
||||||
this._userDuration.classList.remove("unfocused");
|
this._userExpiry.classList.remove("unfocused");
|
||||||
this._invDuration.classList.add("unfocused");
|
this._invDuration.classList.add("unfocused");
|
||||||
invSpan.classList.add("!normal");
|
invSpan.classList.add("!normal");
|
||||||
invSpan.classList.remove("!high");
|
invSpan.classList.remove("!high");
|
||||||
@ -748,9 +747,9 @@ export class createInvite {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this._userDurationButton.checked = false;
|
this._userExpiryButton.checked = false;
|
||||||
this._invDurationButton.checked = true;
|
this._invDurationButton.checked = true;
|
||||||
this._userDurationButton.onchange = checkDuration;
|
this._userExpiryButton.onchange = checkDuration;
|
||||||
this._invDurationButton.onchange = checkDuration;
|
this._invDurationButton.onchange = checkDuration;
|
||||||
|
|
||||||
this._days.onchange = this._checkDurationValidity;
|
this._days.onchange = this._checkDurationValidity;
|
||||||
|
@ -694,6 +694,7 @@ class EmailEditor {
|
|||||||
private _form = document.getElementById("form-editor") as HTMLFormElement;
|
private _form = document.getElementById("form-editor") as HTMLFormElement;
|
||||||
private _header = document.getElementById("header-editor") as HTMLSpanElement;
|
private _header = document.getElementById("header-editor") as HTMLSpanElement;
|
||||||
private _variables = document.getElementById("editor-variables") as HTMLDivElement;
|
private _variables = document.getElementById("editor-variables") as HTMLDivElement;
|
||||||
|
private _variablesLabel = document.getElementById("label-editor-variables") as HTMLElement;
|
||||||
private _textArea = document.getElementById("textarea-editor") as HTMLTextAreaElement;
|
private _textArea = document.getElementById("textarea-editor") as HTMLTextAreaElement;
|
||||||
private _preview = document.getElementById("editor-preview") as HTMLDivElement;
|
private _preview = document.getElementById("editor-preview") as HTMLDivElement;
|
||||||
private _previewContent: HTMLElement;
|
private _previewContent: HTMLElement;
|
||||||
@ -745,6 +746,11 @@ class EmailEditor {
|
|||||||
let ci = i % colors.length;
|
let ci = i % colors.length;
|
||||||
innerHTML += '<span class="button ~' + colors[ci] +' !normal mb-1" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
|
innerHTML += '<span class="button ~' + colors[ci] +' !normal mb-1" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
|
||||||
}
|
}
|
||||||
|
if (this._templ.variables.length == 0) {
|
||||||
|
this._variablesLabel.classList.add("unfocused");
|
||||||
|
} else {
|
||||||
|
this._variablesLabel.classList.remove("unfocused");
|
||||||
|
}
|
||||||
this._variables.innerHTML = innerHTML
|
this._variables.innerHTML = innerHTML
|
||||||
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
|
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
|
||||||
for (let i = 0; i < this._templ.variables.length; i++) {
|
for (let i = 0; i < this._templ.variables.length; i++) {
|
||||||
@ -761,10 +767,12 @@ class EmailEditor {
|
|||||||
}
|
}
|
||||||
loadPreview = () => {
|
loadPreview = () => {
|
||||||
let content = this._textArea.value;
|
let content = this._textArea.value;
|
||||||
for (let variable of this._templ.variables) {
|
if (this._templ.variables) {
|
||||||
let value = this._templ.values[variable.slice(1, -1)];
|
for (let variable of this._templ.variables) {
|
||||||
if (value === undefined) { value = variable; }
|
let value = this._templ.values[variable.slice(1, -1)];
|
||||||
content = content.replace(new RegExp(variable, "g"), value);
|
if (value === undefined) { value = variable; }
|
||||||
|
content = content.replace(new RegExp(variable, "g"), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this._templ.html == "") {
|
if (this._templ.html == "") {
|
||||||
content = stripMarkdown(content);
|
content = stripMarkdown(content);
|
||||||
@ -785,7 +793,7 @@ class EmailEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showList = () => {
|
showList = () => {
|
||||||
_get("/config/emails", null, (req: XMLHttpRequest) => {
|
_get("/config/emails?lang=" + window.language, null, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status != 200) {
|
if (req.status != 200) {
|
||||||
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
||||||
|
113
userdaemon.go
Normal file
113
userdaemon.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hrfee/jfa-go/mediabrowser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userDaemon struct {
|
||||||
|
Stopped bool
|
||||||
|
ShutdownChannel chan string
|
||||||
|
Interval time.Duration
|
||||||
|
period time.Duration
|
||||||
|
app *appContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUserDaemon(interval time.Duration, app *appContext) *userDaemon {
|
||||||
|
return &userDaemon{
|
||||||
|
Stopped: false,
|
||||||
|
ShutdownChannel: make(chan string),
|
||||||
|
Interval: interval,
|
||||||
|
period: interval,
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *userDaemon) run() {
|
||||||
|
rt.app.info.Println("User daemon started")
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-rt.ShutdownChannel:
|
||||||
|
rt.ShutdownChannel <- "Down"
|
||||||
|
return
|
||||||
|
case <-time.After(rt.period):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
started := time.Now()
|
||||||
|
rt.app.storage.loadInvites()
|
||||||
|
rt.app.checkUsers()
|
||||||
|
finished := time.Now()
|
||||||
|
duration := finished.Sub(started)
|
||||||
|
rt.period = rt.Interval - duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *appContext) checkUsers() {
|
||||||
|
if len(app.storage.users) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.info.Println("Daemon: Checking for user expiry")
|
||||||
|
users, status, err := app.jf.GetUsers(false)
|
||||||
|
if err != nil || status != 200 {
|
||||||
|
app.err.Printf("Failed to get users (%d): %s", status, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mode := "disable"
|
||||||
|
termPlural := "Disabling"
|
||||||
|
if app.config.Section("user_expiry").Key("behaviour").MustString("disable_user") == "delete_user" {
|
||||||
|
mode = "delete"
|
||||||
|
termPlural = "Deleting"
|
||||||
|
}
|
||||||
|
email := false
|
||||||
|
if emailEnabled && app.config.Section("user_expiry").Key("send_email").MustBool(true) {
|
||||||
|
email = true
|
||||||
|
}
|
||||||
|
for id, expiry := range app.storage.users {
|
||||||
|
if time.Now().After(expiry) {
|
||||||
|
found := false
|
||||||
|
var user mediabrowser.User
|
||||||
|
for _, u := range users {
|
||||||
|
if u.ID == id {
|
||||||
|
found = true
|
||||||
|
user = u
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
app.info.Printf("Expired user already deleted, ignoring.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app.info.Printf("%s expired user \"%s\"", termPlural, user.Name)
|
||||||
|
if mode == "delete" {
|
||||||
|
status, err = app.jf.DeleteUser(id)
|
||||||
|
} else if mode == "disable" {
|
||||||
|
user.Policy.IsDisabled = true
|
||||||
|
status, err = app.jf.SetPolicy(id, user.Policy)
|
||||||
|
}
|
||||||
|
if !(status == 200 || status == 204) || err != nil {
|
||||||
|
app.err.Printf("Failed to %s \"%s\" (%d): %s", mode, user.Name, status, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(app.storage.users, id)
|
||||||
|
if email {
|
||||||
|
address, ok := app.storage.emails[id]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg, err := app.email.constructUserExpired(app, false)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to construct expiry email for \"%s\": %s", user.Name, err)
|
||||||
|
} else if err := app.email.send(msg, address.(string)); err != nil {
|
||||||
|
app.err.Printf("Failed to send expiry email to \"%s\": %s", user.Name, err)
|
||||||
|
} else {
|
||||||
|
app.info.Printf("Sent expiry notification to \"%s\"", address.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = app.storage.storeUsers()
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to store user duration: %s", err)
|
||||||
|
}
|
||||||
|
}
|
41
views.go
41
views.go
@ -81,6 +81,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
"strings": app.storage.lang.Admin[lang].Strings,
|
"strings": app.storage.lang.Admin[lang].Strings,
|
||||||
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
||||||
"language": app.storage.lang.Admin[lang].JSON,
|
"language": app.storage.lang.Admin[lang].JSON,
|
||||||
|
"langName": lang,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,26 +168,26 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
email = ""
|
email = ""
|
||||||
}
|
}
|
||||||
gcHTML(gc, http.StatusOK, "form-loader.html", gin.H{
|
gcHTML(gc, http.StatusOK, "form-loader.html", gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
"urlBase": app.getURLBase(gc),
|
||||||
"cssClass": app.cssClass,
|
"cssClass": app.cssClass,
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
"helpMessage": app.config.Section("ui").Key("help_message").String(),
|
"helpMessage": app.config.Section("ui").Key("help_message").String(),
|
||||||
"successMessage": app.config.Section("ui").Key("success_message").String(),
|
"successMessage": app.config.Section("ui").Key("success_message").String(),
|
||||||
"jfLink": app.config.Section("jellyfin").Key("public_server").String(),
|
"jfLink": app.config.Section("jellyfin").Key("public_server").String(),
|
||||||
"validate": app.config.Section("password_validation").Key("enabled").MustBool(false),
|
"validate": app.config.Section("password_validation").Key("enabled").MustBool(false),
|
||||||
"requirements": app.validator.getCriteria(),
|
"requirements": app.validator.getCriteria(),
|
||||||
"email": email,
|
"email": email,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"strings": app.storage.lang.Form[lang].Strings,
|
"strings": app.storage.lang.Form[lang].Strings,
|
||||||
"validationStrings": app.storage.lang.Form[lang].validationStringsJSON,
|
"validationStrings": app.storage.lang.Form[lang].validationStringsJSON,
|
||||||
"notifications": app.storage.lang.Form[lang].notificationsJSON,
|
"notifications": app.storage.lang.Form[lang].notificationsJSON,
|
||||||
"code": code,
|
"code": code,
|
||||||
"confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false),
|
"confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false),
|
||||||
"userDuration": inv.UserDuration,
|
"userExpiry": inv.UserExpiry,
|
||||||
"userDurationDays": inv.UserDays,
|
"userExpiryDays": inv.UserDays,
|
||||||
"userDurationHours": inv.UserHours,
|
"userExpiryHours": inv.UserHours,
|
||||||
"userDurationMinutes": inv.UserMinutes,
|
"userExpiryMinutes": inv.UserMinutes,
|
||||||
"userDurationMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
|
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user