1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-10-18 17:10:11 +00:00

Compare commits

..

No commits in common. "fa7da1b23f53c0b96eea860d18663bd734031a01" and "3635b6a3677036297983c9d0f548ec688c2b9a76" have entirely different histories.

28 changed files with 112 additions and 873 deletions

99
api.go
View File

@ -445,48 +445,10 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
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
return
}
// @Summary Extend time before the user(s) expiry.
// @Produce json
// @Param extendExpiryDTO body extendExpiryDTO true "Extend expiry object"
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /users/extend [post]
// @tags Users
func (app *appContext) ExtendExpiry(gc *gin.Context) {
var req extendExpiryDTO
app.info.Printf("Expiry extension requested for %d user(s)", len(req.Users))
gc.BindJSON(&req)
if req.Days == 0 && req.Hours == 0 && req.Minutes == 0 {
respondBool(400, false, gc)
return
}
for _, id := range req.Users {
if expiry, ok := app.storage.users[id]; ok {
app.storage.users[id] = expiry.Add(time.Duration(60*(req.Days*24+req.Hours)+req.Minutes) * time.Minute)
app.debug.Printf("Expiry extended for \"%s\"", id)
}
}
if err := app.storage.storeUsers(); err != nil {
app.err.Printf("Failed to store user duration: %s", err)
respondBool(500, false, gc)
return
}
respondBool(204, true, gc)
}
// @Summary Creates a new Jellyfin user via invite code
// @Produce json
// @Param newUserDTO body newUserDTO true "New user request object"
@ -672,12 +634,6 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
} else {
invite.RemainingUses = 1
}
invite.UserExpiry = req.UserExpiry
if invite.UserExpiry {
invite.UserDays = req.UserDays
invite.UserHours = req.UserHours
invite.UserMinutes = req.UserMinutes
}
invite.ValidTill = validTill
if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
app.debug.Printf("%s: Sending invite email", inviteCode)
@ -794,7 +750,6 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
app.info.Println("Profile creation requested")
var req newProfileDTO
gc.BindJSON(&req)
app.jf.CacheExpiry = time.Now()
user, status, err := app.jf.UserByID(req.ID, false)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin (%d): %s", status, err)
@ -862,10 +817,6 @@ func (app *appContext) GetInvites(gc *gin.Context) {
Days: days,
Hours: hours,
Minutes: minutes,
UserExpiry: inv.UserExpiry,
UserDays: inv.UserDays,
UserHours: inv.UserHours,
UserMinutes: inv.UserMinutes,
Created: app.formatDatetime(inv.Created),
Profile: inv.Profile,
NoLimit: inv.NoLimit,
@ -1032,7 +983,6 @@ func (app *appContext) GetUsers(gc *gin.Context) {
ID: jfUser.ID,
Name: jfUser.Name,
Admin: jfUser.Policy.IsAdministrator,
Disabled: jfUser.Policy.IsDisabled,
}
user.LastActive = "n/a"
if !jfUser.LastActivityDate.IsZero() {
@ -1041,9 +991,6 @@ func (app *appContext) GetUsers(gc *gin.Context) {
if email, ok := app.storage.emails[jfUser.ID]; ok {
user.Email = email.(string)
}
if expiry, ok := app.storage.users[jfUser.ID]; ok {
user.Expiry = app.formatDatetime(expiry)
}
resp.UserList = append(resp.UserList, user)
}
@ -1324,24 +1271,18 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
// @Summary Get a list of email names and IDs.
// @Produce json
// @Param lang query string false "Language for email titles."
// @Success 200 {object} emailListDTO
// @Router /config/emails [get]
// @tags Configuration
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{
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.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},
"UserCreated": {Name: app.storage.lang.Email["en-us"].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
"InviteExpiry": {Name: app.storage.lang.Email["en-us"].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
"PasswordReset": {Name: app.storage.lang.Email["en-us"].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
"UserDeleted": {Name: app.storage.lang.Email["en-us"].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
"InviteEmail": {Name: app.storage.lang.Email["en-us"].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
"WelcomeEmail": {Name: app.storage.lang.Email["en-us"].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
"EmailConfirmation": {Name: app.storage.lang.Email["en-us"].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
})
}
@ -1382,9 +1323,6 @@ func (app *appContext) SetEmail(gc *gin.Context) {
} else if id == "EmailConfirmation" {
app.storage.customEmails.EmailConfirmation.Content = req.Content
app.storage.customEmails.EmailConfirmation.Enabled = true
} else if id == "UserExpired" {
app.storage.customEmails.UserExpired.Content = req.Content
app.storage.customEmails.UserExpired.Enabled = true
} else {
respondBool(400, false, gc)
return
@ -1426,8 +1364,6 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
app.storage.customEmails.WelcomeEmail.Enabled = enabled
} else if id == "EmailConfirmation" {
app.storage.customEmails.EmailConfirmation.Enabled = enabled
} else if id == "UserExpired" {
app.storage.customEmails.UserExpired.Enabled = enabled
} else {
respondBool(400, false, gc)
return
@ -1447,7 +1383,6 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
// @Router /config/emails/{id} [get]
// @tags Configuration
func (app *appContext) GetEmail(gc *gin.Context) {
lang := app.storage.lang.chosenEmailLang
id := gc.Param("id")
var content string
var err error
@ -1456,8 +1391,8 @@ func (app *appContext) GetEmail(gc *gin.Context) {
var values map[string]interface{}
var writeVars func(variables []string)
newEmail := false
username := app.storage.lang.Email[lang].Strings.get("username")
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
username := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("username")
emailAddress := app.storage.lang.Email[app.storage.lang.chosenEmailLang].Strings.get("emailAddress")
if id == "UserCreated" {
content = app.storage.customEmails.UserCreated.Content
if content == "" {
@ -1504,7 +1439,7 @@ func (app *appContext) GetEmail(gc *gin.Context) {
variables = app.storage.customEmails.UserDeleted.Variables
}
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
values = app.email.deletedValues(app.storage.lang.Email[lang].UserDeleted.get("reason"), app, false)
values = app.email.deletedValues(app.storage.lang.Email[app.storage.lang.chosenEmailLang].UserDeleted.get("reason"), app, false)
// app.storage.customEmails.UserDeleted = content
} else if id == "InviteEmail" {
content = app.storage.customEmails.InviteEmail.Content
@ -1542,17 +1477,6 @@ func (app *appContext) GetEmail(gc *gin.Context) {
writeVars = func(variables []string) { app.storage.customEmails.EmailConfirmation.Variables = variables }
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
// 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 {
respondBool(400, false, gc)
return
@ -1581,9 +1505,6 @@ func (app *appContext) GetEmail(gc *gin.Context) {
}
writeVars(variables)
}
if variables == nil {
variables = []string{}
}
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return

View File

@ -22,10 +22,6 @@ func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
return os.DirFS(dir), file
}
func (app *appContext) MustSetValue(section, key, val string) {
app.config.Section(section).Key(key).SetValue(app.config.Section(section).Key(key).MustString(val))
}
func (app *appContext) loadConfig() error {
var err error
app.config, err = ini.Load(app.configPath)
@ -33,46 +29,42 @@ func (app *appContext) loadConfig() error {
return err
}
app.MustSetValue("jellyfin", "public_server", app.config.Section("jellyfin").Key("server").String())
app.config.Section("jellyfin").Key("public_server").SetValue(app.config.Section("jellyfin").Key("public_server").MustString(app.config.Section("jellyfin").Key("server").String()))
for _, key := range app.config.Section("files").Keys() {
if name := key.Name(); name != "html_templates" && name != "lang_files" {
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", "users"} {
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails"} {
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.config.Section("email").Key("no_username").SetValue(strconv.FormatBool(app.config.Section("email").Key("no_username").MustBool(false)))
app.MustSetValue("password_resets", "email_html", "jfa-go:"+"email.html")
app.MustSetValue("password_resets", "email_text", "jfa-go:"+"email.txt")
app.config.Section("password_resets").Key("email_html").SetValue(app.config.Section("password_resets").Key("email_html").MustString("jfa-go:" + "email.html"))
app.config.Section("password_resets").Key("email_text").SetValue(app.config.Section("password_resets").Key("email_text").MustString("jfa-go:" + "email.txt"))
app.MustSetValue("invite_emails", "email_html", "jfa-go:"+"invite-email.html")
app.MustSetValue("invite_emails", "email_text", "jfa-go:"+"invite-email.txt")
app.config.Section("invite_emails").Key("email_html").SetValue(app.config.Section("invite_emails").Key("email_html").MustString("jfa-go:" + "invite-email.html"))
app.config.Section("invite_emails").Key("email_text").SetValue(app.config.Section("invite_emails").Key("email_text").MustString("jfa-go:" + "invite-email.txt"))
app.MustSetValue("email_confirmation", "email_html", "jfa-go:"+"confirmation.html")
app.MustSetValue("email_confirmation", "email_text", "jfa-go:"+"confirmation.txt")
app.config.Section("email_confirmation").Key("email_html").SetValue(app.config.Section("email_confirmation").Key("email_html").MustString("jfa-go:" + "confirmation.html"))
app.config.Section("email_confirmation").Key("email_text").SetValue(app.config.Section("email_confirmation").Key("email_text").MustString("jfa-go:" + "confirmation.txt"))
app.MustSetValue("notifications", "expiry_html", "jfa-go:"+"expired.html")
app.MustSetValue("notifications", "expiry_text", "jfa-go:"+"expired.txt")
app.config.Section("notifications").Key("expiry_html").SetValue(app.config.Section("notifications").Key("expiry_html").MustString("jfa-go:" + "expired.html"))
app.config.Section("notifications").Key("expiry_text").SetValue(app.config.Section("notifications").Key("expiry_text").MustString("jfa-go:" + "expired.txt"))
app.MustSetValue("notifications", "created_html", "jfa-go:"+"created.html")
app.MustSetValue("notifications", "created_text", "jfa-go:"+"created.txt")
app.config.Section("notifications").Key("created_html").SetValue(app.config.Section("notifications").Key("created_html").MustString("jfa-go:" + "created.html"))
app.config.Section("notifications").Key("created_text").SetValue(app.config.Section("notifications").Key("created_text").MustString("jfa-go:" + "created.txt"))
app.MustSetValue("deletion", "email_html", "jfa-go:"+"deleted.html")
app.MustSetValue("deletion", "email_text", "jfa-go:"+"deleted.txt")
app.config.Section("deletion").Key("email_html").SetValue(app.config.Section("deletion").Key("email_html").MustString("jfa-go:" + "deleted.html"))
app.config.Section("deletion").Key("email_text").SetValue(app.config.Section("deletion").Key("email_text").MustString("jfa-go:" + "deleted.txt"))
app.MustSetValue("welcome_email", "email_html", "jfa-go:"+"welcome.html")
app.MustSetValue("welcome_email", "email_text", "jfa-go:"+"welcome.txt")
app.config.Section("welcome_email").Key("email_html").SetValue(app.config.Section("welcome_email").Key("email_html").MustString("jfa-go:" + "welcome.html"))
app.config.Section("welcome_email").Key("email_text").SetValue(app.config.Section("welcome_email").Key("email_text").MustString("jfa-go:" + "welcome.txt"))
app.MustSetValue("template_email", "email_html", "jfa-go:"+"template.html")
app.MustSetValue("template_email", "email_text", "jfa-go:"+"template.txt")
app.MustSetValue("user_expiry", "behaviour", "disable_user")
app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html")
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
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("jellyfin").Key("version").SetValue(version)
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")

View File

@ -765,63 +765,6 @@
}
}
},
"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": {
"order": [],
"meta": {
@ -879,14 +822,6 @@
"value": "",
"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": {
"name": "Ombi user template",
"required": false,

View File

@ -4,7 +4,7 @@ import "time"
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
type inviteDaemon struct {
type repeater struct {
Stopped bool
ShutdownChannel chan string
Interval time.Duration
@ -12,8 +12,8 @@ type inviteDaemon struct {
app *appContext
}
func newInviteDaemon(interval time.Duration, app *appContext) *inviteDaemon {
return &inviteDaemon{
func newRepeater(interval time.Duration, app *appContext) *repeater {
return &repeater{
Stopped: false,
ShutdownChannel: make(chan string),
Interval: interval,
@ -22,7 +22,7 @@ func newInviteDaemon(interval time.Duration, app *appContext) *inviteDaemon {
}
}
func (rt *inviteDaemon) run() {
func (rt *repeater) run() {
rt.app.info.Println("Invite daemon started")
for {
select {
@ -42,7 +42,7 @@ func (rt *inviteDaemon) run() {
}
}
func (rt *inviteDaemon) shutdown() {
func (rt *repeater) shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel

View File

@ -569,42 +569,6 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext, noSub
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.
func (emailer *Emailer) send(email *Email, address ...string) error {
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)

View File

@ -9,7 +9,6 @@
window.ombiEnabled = {{ .ombiEnabled }};
window.usernameEnabled = {{ .username }};
window.langFile = JSON.parse({{ .language }});
window.language = "{{ .langName }}";
</script>
{{ template "header.html" . }}
<title>Admin - jfa-go</title>
@ -94,35 +93,6 @@
</div>
</form>
</div>
<div id="modal-extend-expiry" class="modal">
<form class="modal-content card" id="form-extend-expiry" href="">
<span class="heading"><span id="header-extend-expiry"></span> <span class="modal-close">&times;</span></span>
<div class="content mt-half">
<label class="label supra" for="extend-expiry-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="extend-expiry-days">
<option>0</option>
</select>
</div>
<label class="label supra" for="extend-expiry-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="extend-expiry-hours">
<option>0</option>
</select>
</div>
<label class="label supra" for="extend-expiry-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="extend-expiry-minutes">
<option>0</option>
</select>
</div>
<label>
<input type="submit" class="unfocused">
<span class="button ~critical !normal full-width center supra submit">{{ .strings.submit }}</span>
</label>
</div>
</form>
</div>
<div id="modal-announce" class="modal">
<form class="modal-content card" id="form-announce" href="">
<span class="heading"><span id="header-announce"></span> <span class="modal-close">&times;</span></span>
@ -162,7 +132,7 @@
<span class="heading"><span id="header-editor"></span> <span class="modal-close">&times;</span></span>
<div class="row">
<div class="col flex-col content mt-half">
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
<span class="label supra" for="editor-variables">{{ .strings.variables }}</span>
<div id="editor-variables"></div>
<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>
@ -289,17 +259,6 @@
<span class="heading">{{ .strings.create }}</span>
<div class="row" id="create-inv">
<div class="card ~neutral !normal col">
<div class="flex-row mb-1">
<label class="flex-row-group mr-1">
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
<span class="button ~neutral !high supra full-width center">{{ .strings.inviteDuration }}</span>
</label>
<label class="flex-row-group ml-1">
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
<span class="button ~neutral !normal supra full-width center">{{ .strings.userExpiry }}</span>
</label>
</div>
<div id="inv-duration">
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="create-days">
@ -318,34 +277,6 @@
<option>0</option>
</select>
</div>
</div>
<div id="user-expiry" class="unfocused">
<p class="support">{{ .strings.userExpiryDescription }}</p>
<div class="mb-half">
<label for="create-user-expiry-enabled" class="button ~neutral !normal">
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
<span class="ml-half">{{ .strings.enabled }} </span>
</label>
</div>
<label class="label supra" for="user-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="user-days">
<option>0</option>
</select>
</div>
<label class="label supra" for="user-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="user-hours">
<option>0</option>
</select>
</div>
<label class="label supra" for="user-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="user-minutes">
<option>0</option>
</select>
</div>
</div>
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
<input type="text" id="create-label" class="input ~neutral !normal mb-1 mt-half">
</div>
@ -382,11 +313,10 @@
<div class="card ~neutral !low accounts mb-1">
<span class="heading">{{ .strings.accounts }}</span>
<div class="fr">
<span class="button ~neutral !normal mb-half" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<span class="button ~info !normal mb-half" id="accounts-announce">{{ .strings.announce }}</span>
<span class="button ~urge !normal mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="button ~warning !normal mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
<span class="button ~critical !normal mb-half" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
<span class="button ~neutral !normal" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<span class="button ~info !normal" id="accounts-announce">{{ .strings.announce }}</span>
<span class="button ~urge !normal" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="button ~critical !normal" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div>
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
<table class="table">
@ -395,7 +325,6 @@
<th><input type="checkbox" value="" id="accounts-select-all"></th>
<th>{{ .strings.username }}</th>
<th>{{ .strings.emailAddress }}</th>
<th>{{ .strings.expiry }}</th>
<th>{{ .strings.lastActiveTime }}</th>
</tr>
</thead>

View File

@ -7,11 +7,6 @@
window.code = "{{ .code }}";
window.messages = JSON.parse({{ .notifications }});
window.confirmation = {{ .confirmation }};
window.userExpiryEnabled = {{ .userExpiry }};
window.userExpiryDays = {{ .userExpiryDays }};
window.userExpiryHours = {{ .userExpiryHours }};
window.userExpiryMinutes = {{ .userExpiryMinutes }};
window.userExpiryMessage = {{ .userExpiryMessage }};
</script>
<script src="js/form.js" type="module"></script>
{{ end }}

View File

@ -37,9 +37,6 @@
</div>
<div class="row">
<div class="col">
{{ if .userExpiry }}
<aside class="col aside sm ~warning" id="user-expiry-message"></aside>
{{ end }}
<form class="card ~neutral !normal" id="form-create" href="">
<label class="label supra">
{{ .strings.username }}
@ -47,13 +44,13 @@
</label>
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
<input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
<input type="email" class="input ~neutral 1high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
<label class="label supra" for="create-password">{{ .strings.password }}</label>
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
<input type="password" class="input ~neutral 1high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
<label class="label supra" for="create-reenter-password">{{ .strings.reEnterPassword }}</label>
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
<input type="password" class="input ~neutral 1high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.createAccountButton }}</span>

View File

@ -83,7 +83,6 @@ type emailLang struct {
InviteEmail langSection `json:"inviteEmail"`
WelcomeEmail langSection `json:"welcomeEmail"`
EmailConfirmation langSection `json:"emailConfirmation"`
UserExpired langSection `json:"userExpired"`
}
type setupLangs map[string]setupLang

View File

@ -10,7 +10,6 @@
"inviteHours": "Hours",
"inviteMinutes": "Minutes",
"inviteNumberOfUses": "Number of uses",
"inviteDuration": "Invite Duration",
"warning": "Warning",
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
"inviteSendToEmail": "Send to",
@ -21,15 +20,9 @@
"delete": "Delete",
"name": "Name",
"date": "Date",
"enabled": "Enabled",
"disabled": "Disabled",
"admin": "Admin",
"lastActiveTime": "Last Active",
"from": "From",
"user": "User",
"expiry": "Expiry",
"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",
"version": "Version",
"commitNoun": "Commit",
@ -44,7 +37,6 @@
"preview": "Preview",
"reset": "Reset",
"edit": "Edit",
"extendExpiry": "Extend expiry",
"customizeEmails": "Customize Emails",
"customizeEmailsDescription": "If you don't want to use jfa-go's email templates, you can create your own using Markdown.",
"markdownSupported": "Markdown is supported.",
@ -145,14 +137,6 @@
"appliedSettings": {
"singular": "Applied settings to {n} user.",
"plural": "Applied settings to {n} users."
},
"extendExpiry": {
"singular": "Extend expiry for {n} user",
"plural": "Extend expiry for {n} users"
},
"extendedExpiry": {
"singular": "Extended expiry for {n} user.",
"plural": "Extended expiry for {n} users."
}
}
}

View File

@ -55,11 +55,5 @@
"title": "Confirm your email - Jellyfin",
"clickBelow": "Click the link below to confirm your email address and start using Jellyfin.",
"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."
}
}

View File

@ -16,8 +16,7 @@
"successHeader": "Success!",
"successContinueButton": "Continue",
"confirmationRequired": "Email confirmation required",
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}."
"confirmationRequiredMessage": "Please check your email inbox to verify your address."
},
"notifications": {
"errorUserExists": "User already exists.",

View File

@ -1,76 +0,0 @@
<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>

View File

@ -1,5 +0,0 @@
{{ .yourAccountHasExpired }}
{{ .contactTheAdmin }}
{{ .message }}

View File

@ -338,8 +338,6 @@ func start(asDaemon, firstCall bool) {
app.storage.loadConfiguration()
app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String()
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.loadProfiles()
@ -512,12 +510,9 @@ func start(asDaemon, firstCall bool) {
os.Exit(0)
}
inviteDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
inviteDaemon := newRepeater(time.Duration(60*time.Second), app)
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 {
go app.StartPWR()
}

View File

@ -33,10 +33,6 @@ type generateInviteDTO struct {
Days int `json:"days" example:"1"` // Number of days
Hours int `json:"hours" example:"2"` // Number of hours
Minutes int `json:"minutes" example:"3"` // Number of minutes
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
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
Email string `json:"email" example:"jeff@jellyf.in"` // Send invite to this address
MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses
NoLimit bool `json:"no-limit" example:"false"` // No invite use limit
@ -76,10 +72,6 @@ type inviteDTO struct {
Days int `json:"days" example:"1"` // Number of days till expiry
Hours int `json:"hours" example:"2"` // Number of hours till expiry
Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
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
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
Created string `json:"created" example:"01/01/20 12:00"` // Date of creation
Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite
UsedBy [][]string `json:"used-by,omitempty"` // Users who have used this invite
@ -114,8 +106,6 @@ type respUser struct {
Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available)
LastActive string `json:"last_active"` // Time of last activity on Jellyfin
Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
Expiry string `json:"expiry" example:"01/02/21 12:00"` // Expiry time of user, if applicable.
Disabled bool `json:"disabled"` // Whether or not the user is disabled.
}
type getUsersDTO struct {
@ -207,10 +197,3 @@ type customEmailDTO struct {
HTML string `json:"html"`
Plaintext string `json:"plaintext"`
}
type extendExpiryDTO struct {
Users []string `json:"users"` // List of user IDs to apply to.
Days int `json:"days" example:"1"` // Number of days to add.
Hours int `json:"hours" example:"2"` // Number of hours to add.
Minutes int `json:"minutes" example:"3"` // Number of minutes to add.
}

6
package-lock.json generated
View File

@ -236,9 +236,9 @@
"integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY="
},
"esbuild": {
"version": "0.8.53",
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.53.tgz",
"integrity": "sha1-tAi7DKGynasT2Lv31Z9Zr+Z3boY="
"version": "0.8.50",
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.50.tgz",
"integrity": "sha1-6/JP3gza0aNpeJ3W/XqCCwoB5Gw="
},
"escalade": {
"version": "3.1.1",

View File

@ -19,7 +19,7 @@
"dependencies": {
"@ts-stack/markdown": "^1.3.0",
"a17t": "^0.4.0",
"esbuild": "^0.8.53",
"esbuild": "^0.8.50",
"lodash": "^4.17.19",
"mjml": "^4.8.0",
"remixicon": "^2.5.0",

View File

@ -126,7 +126,6 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.DELETE(p+"/users", app.DeleteUsers)
api.GET(p+"/users", app.GetUsers)
api.POST(p+"/users", app.NewUserAdmin)
api.POST(p+"/users/extend", app.ExtendExpiry)
api.POST(p+"/invites", app.GenerateInvite)
api.GET(p+"/invites", app.GetInvites)
api.DELETE(p+"/invites", app.DeleteInvite)

View File

@ -15,8 +15,7 @@ import (
type Storage struct {
timePattern string
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path string
users map[string]time.Time
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path string
invites Invites
profiles map[string]Profile
defaultProfile string
@ -35,7 +34,6 @@ type customEmails struct {
InviteEmail customEmail `json:"inviteEmail"`
WelcomeEmail customEmail `json:"welcomeEmail"`
EmailConfirmation customEmail `json:"emailConfirmation"`
UserExpired customEmail `json:"userExpired"`
}
type customEmail struct {
@ -61,10 +59,6 @@ type Invite struct {
NoLimit bool `json:"no-limit"`
RemainingUses int `json:"remaining-uses"`
ValidTill time.Time `json:"valid_till"`
UserExpiry bool `json:"user-duration"`
UserDays int `json:"user-days,omitempty"`
UserHours int `json:"user-hours,omitempty"`
UserMinutes int `json:"user-minutes,omitempty"`
Email string `json:"email"`
UsedBy [][]string `json:"used-by"`
Notify map[string]map[string]bool `json:"notify"`
@ -409,14 +403,6 @@ func (st *Storage) storeInvites() error {
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 {
return loadJSON(st.emails_path, &st.emails)
}

View File

@ -57,8 +57,6 @@ window.availableProfiles = window.availableProfiles || [];
window.modals.editor = new Modal(document.getElementById("modal-editor"));
window.modals.customizeEmails = new Modal(document.getElementById("modal-customize"));
window.modals.extendExpiry = new Modal(document.getElementById("modal-extend-expiry"));
})();
var inviteCreator = new createInvite();

View File

@ -10,11 +10,6 @@ interface formWindow extends Window {
messages: { [key: string]: string };
confirmation: boolean;
confirmationModal: Modal
userExpiryEnabled: boolean;
userExpiryDays: number;
userExpiryHours: number;
userExpiryMinutes: number;
userExpiryMessage: string;
}
interface pwValString {
@ -39,19 +34,6 @@ if (window.confirmation) {
}
declare var window: formWindow;
if (window.userExpiryEnabled) {
const messageEl = document.getElementById("user-expiry-message") as HTMLElement;
const calculateTime = () => {
let time = new Date()
time.setDate(time.getDate() + window.userExpiryDays);
time.setHours(time.getHours() + window.userExpiryHours);
time.setMinutes(time.getMinutes() + window.userExpiryMinutes);
messageEl.textContent = window.userExpiryMessage.replace("{date}", time.toDateString() + " " + time.toLocaleTimeString());
setTimeout(calculateTime, 1000);
};
calculateTime();
}
var defaultPwValStrings: pwValStrings = {
length: {
singular: "Must have at least {n} character",

View File

@ -6,8 +6,6 @@ interface User {
email: string | undefined;
last_active: string;
admin: boolean;
disabled: boolean;
expiry: string;
}
class user implements User {
@ -15,11 +13,9 @@ class user implements User {
private _check: HTMLInputElement;
private _username: HTMLSpanElement;
private _admin: HTMLSpanElement;
private _disabled: HTMLSpanElement;
private _email: HTMLInputElement;
private _emailAddress: string;
private _emailEditButton: HTMLElement;
private _expiry: HTMLTableDataCellElement;
private _lastActive: HTMLTableDataCellElement;
id: string;
private _selected: boolean;
@ -38,21 +34,10 @@ class user implements User {
set admin(state: boolean) {
if (state) {
this._admin.classList.add("chip", "~info", "ml-1");
this._admin.textContent = window.lang.strings("admin");
this._admin.textContent = "Admin";
} else {
this._admin.classList.remove("chip", "~info", "ml-1");
this._admin.textContent = "";
}
}
get disabled(): boolean { return this._disabled.classList.contains("chip"); }
set disabled(state: boolean) {
if (state) {
this._disabled.classList.add("chip", "~warning", "ml-1");
this._disabled.textContent = window.lang.strings("disabled");
} else {
this._disabled.classList.remove("chip", "~warning", "ml-1");
this._disabled.textContent = "";
this._admin.textContent = ""
}
}
@ -67,9 +52,6 @@ class user implements User {
}
}
get expiry(): string { return this._expiry.textContent; }
set expiry(value: string) { this._expiry.textContent = value; }
get last_active(): string { return this._lastActive.textContent; }
set last_active(value: string) { this._lastActive.textContent = value; }
@ -80,19 +62,16 @@ class user implements User {
this._row = document.createElement("tr") as HTMLTableRowElement;
this._row.innerHTML = `
<td><input type="checkbox" value=""></td>
<td><span class="accounts-username"></span> <span class="accounts-admin"></span> <span class="accounts-disabled"></span></td>
<td><span class="accounts-username"></span> <span class="accounts-admin"></span></td>
<td><i class="icon ri-edit-line accounts-email-edit"></i><span class="accounts-email-container ml-half"></span></td>
<td class="accounts-expiry"></td>
<td class="accounts-last-active"></td>
`;
const emailEditor = `<input type="email" class="input ~neutral !normal stealth-input">`;
this._check = this._row.querySelector("input[type=checkbox]") as HTMLInputElement;
this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement;
this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement;
this._disabled = this._row.querySelector(".accounts-disabled") as HTMLSpanElement;
this._email = this._row.querySelector(".accounts-email-container") as HTMLInputElement;
this._emailEditButton = this._row.querySelector(".accounts-email-edit") as HTMLElement;
this._expiry = this._row.querySelector(".accounts-expiry") as HTMLTableDataCellElement;
this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement;
this._check.onchange = () => { this.selected = this._check.checked; }
@ -151,8 +130,6 @@ class user implements User {
this.email = user.email || "";
this.last_active = user.last_active;
this.admin = user.admin;
this.disabled = user.disabled;
this.expiry = user.expiry;
}
asElement = (): HTMLTableRowElement => { return this._row; }
@ -175,7 +152,6 @@ export class accountsList {
private _deleteUser = document.getElementById("accounts-delete-user") as HTMLSpanElement;
private _deleteNotify = document.getElementById("delete-user-notify") as HTMLInputElement;
private _deleteReason = document.getElementById("textarea-delete-user") as HTMLTextAreaElement;
private _extendExpiry = document.getElementById("accounts-extend-expiry") as HTMLSpanElement;
private _modifySettings = document.getElementById("accounts-modify-user") as HTMLSpanElement;
private _modifySettingsProfile = document.getElementById("radio-use-profile") as HTMLInputElement;
private _modifySettingsUser = document.getElementById("radio-use-user") as HTMLInputElement;
@ -191,24 +167,6 @@ export class accountsList {
private _addUserEmail = this._addUserForm.querySelector("input[type=email]") as HTMLInputElement;
private _addUserPassword = this._addUserForm.querySelector("input[type=password]") as HTMLInputElement;
private _count = 30;
private _populateNumbers = () => {
const fieldIDs = ["days", "hours", "minutes"];
const prefixes = ["extend-expiry-"];
for (let i = 0; i < fieldIDs.length; i++) {
for (let j = 0; j < prefixes.length; j++) {
const field = document.getElementById(prefixes[j] + fieldIDs[i]);
field.textContent = '';
for (let n = 0; n <= this._count; n++) {
const opt = document.createElement("option") as HTMLOptionElement;
opt.textContent = ""+n;
opt.value = ""+n;
field.appendChild(opt);
}
}
}
}
get selectAll(): boolean { return this._selectAll.checked; }
set selectAll(state: boolean) {
for (let id in this._users) {
@ -235,7 +193,6 @@ export class accountsList {
if (window.emailEnabled) {
this._announceButton.classList.add("unfocused");
}
this._extendExpiry.classList.add("unfocused");
} else {
if (this._checkCount == Object.keys(this._users).length) {
this._selectAll.checked = true;
@ -250,18 +207,6 @@ export class accountsList {
if (window.emailEnabled) {
this._announceButton.classList.remove("unfocused");
}
const list = this._collectUsers();
let anyNonExpiries = false;
for (let id of list) {
if (!this._users[id].expiry) {
anyNonExpiries = true;
this._extendExpiry.classList.add("unfocused");
break;
}
}
if (!anyNonExpiries) {
this._extendExpiry.classList.remove("unfocused");
}
}
}
@ -449,39 +394,7 @@ export class accountsList {
window.modals.modifyUser.show();
}
extendExpiry = () => {
const list = this._collectUsers();
let applyList: string[] = [];
for (let id of list) {
if (this._users[id].expiry) {
applyList.push(id);
}
}
document.getElementById("header-extend-expiry").textContent = window.lang.quantity("extendExpiry", applyList.length);
const form = document.getElementById("form-extend-expiry") as HTMLFormElement;
form.onsubmit = (event: Event) => {
event.preventDefault();
let send = { "users": applyList }
for (let field of ["days", "hours", "minutes"]) {
send[field] = +(document.getElementById("extend-expiry-"+field) as HTMLSelectElement).value;
}
_post("/users/extend", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status != 200 && req.status != 204) {
window.notifications.customError("extendExpiryError", window.lang.notif("errorFailureCheckLogs"));
} else {
window.notifications.customSuccess("extendExpiry", window.lang.quantity("extendedExpiry", applyList.length));
}
window.modals.extendExpiry.close()
this.reload();
}
});
}
window.modals.extendExpiry.show();
}
constructor() {
this._populateNumbers();
this._users = {};
this._selectAll.checked = false;
this._selectAll.onchange = () => { this.selectAll = this._selectAll.checked };
@ -527,9 +440,6 @@ export class accountsList {
this._announceButton.onclick = this.announce;
this._announceButton.classList.add("unfocused");
this._extendExpiry.onclick = this.extendExpiry;
this._extendExpiry.classList.add("unfocused");
if (!window.usernameEnabled) {
this._addUserName.classList.add("unfocused");
this._addUserName = this._addUserEmail;

View File

@ -59,20 +59,7 @@ export class DOMInvite implements Invite {
get expiresIn(): string { return this._expiresIn }
set expiresIn(expiry: string) {
this._expiresIn = expiry;
this._infoArea.querySelector("span.inv-duration").textContent = expiry;
}
private _userExpiry: string;
get userExpiryTime(): string { return this._userExpiry; }
set userExpiryTime(d: string) {
const expiry = this._middle.querySelector("span.user-expiry") as HTMLSpanElement;
if (!d) {
expiry.textContent = "";
} else {
expiry.textContent = window.lang.strings("userExpiry");
}
this._userExpiry = d;
this._middle.querySelector("strong.user-expiry-time").textContent = d;
this._infoArea.querySelector("span.inv-expiry").textContent = expiry;
}
private _remainingUses: string = "1";
@ -278,7 +265,7 @@ export class DOMInvite implements Invite {
<span class="inv-email-chip"><i></i></span>
<span class="content sm"></span>
</div>
<span class="inv-duration mr-1"></span>
<span class="inv-expiry mr-1"></span>
<span class="button ~critical !normal inv-delete">${window.lang.strings("delete")}</span>
<label>
<i class="icon clickable ri-arrow-down-s-line not-rotated"></i>
@ -344,7 +331,6 @@ export class DOMInvite implements Invite {
this._middle.innerHTML = `
<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"><span class="user-expiry"></span> <strong class="user-expiry-time"></strong></p>
`;
this._right = document.createElement('div') as HTMLDivElement;
@ -376,7 +362,6 @@ export class DOMInvite implements Invite {
if (invite.label) {
this.label = invite.label;
}
this.userExpiryTime = invite.userExpiryTime || "";
}
asElement = (): HTMLDivElement => { return this._container; }
@ -477,25 +462,13 @@ function parseInvite(invite: { [f: string]: string | number | string[][] | boole
parsed.email = invite["email"] as string || "";
parsed.label = invite["label"] as string || "";
let time = "";
let userExpiryTime = "";
const fields = ["days", "hours", "minutes"];
let prefixes = [""];
if (invite["user-expiry"] as boolean) { prefixes.push("user-"); }
for (let i = 0; i < fields.length; i++) {
for (let j = 0; j < prefixes.length; j++) {
if (invite[prefixes[j]+fields[i]]) {
let text = `${invite[prefixes[j]+fields[i]]}${fields[i][0]} `;
if (prefixes[j] == "user-") {
userExpiryTime += text;
} else {
time += text;
}
}
if (invite[fields[i]] != 0) {
time += `${invite[fields[i]]}${fields[i][0]} `;
}
}
parsed.expiresIn = window.lang.var("strings", "inviteExpiresInTime", time.slice(0, -1));
parsed.userExpiry = invite["user-expiry"] as boolean;
parsed.userExpiryTime = userExpiryTime.slice(0, -1);
parsed.remainingUses = invite["no-limit"] ? "∞" : String(invite["remaining-uses"])
parsed.usedBy = invite["used-by"] as string[][] || [];
parsed.created = invite["created"] as string || window.lang.strings("unknown");
@ -508,7 +481,6 @@ function parseInvite(invite: { [f: string]: string | number | string[][] | boole
export class createInvite {
private _sendToEnabled = document.getElementById("create-send-to-enabled") as HTMLInputElement;
private _sendTo = document.getElementById("create-send-to") as HTMLInputElement;
private _userExpiryToggle = document.getElementById("create-user-expiry-enabled") as HTMLInputElement;
private _uses = document.getElementById('create-uses') as HTMLInputElement;
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
private _infUsesWarning = document.getElementById('create-inf-uses-warning') as HTMLParagraphElement;
@ -519,14 +491,6 @@ export class createInvite {
private _days = document.getElementById("create-days") as HTMLSelectElement;
private _hours = document.getElementById("create-hours") as HTMLSelectElement;
private _minutes = document.getElementById("create-minutes") as HTMLSelectElement;
private _userDays = document.getElementById("user-days") as HTMLSelectElement;
private _userHours = document.getElementById("user-hours") as HTMLSelectElement;
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
private _invDurationButton = document.getElementById('radio-inv-duration') as HTMLInputElement;
private _userExpiryButton = document.getElementById('radio-user-expiry') as HTMLInputElement;
private _invDuration = document.getElementById('inv-duration');
private _userExpiry = document.getElementById('user-expiry');
// Broadcast when new invite created
private _newInviteEvent = new CustomEvent("newInviteEvent");
@ -534,11 +498,9 @@ export class createInvite {
private _count: Number = 30;
private _populateNumbers = () => {
const fieldIDs = ["days", "hours", "minutes"];
const prefixes = ["create-", "user-"];
const fieldIDs = ["create-days", "create-hours", "create-minutes"];
for (let i = 0; i < fieldIDs.length; i++) {
for (let j = 0; j < prefixes.length; j++) {
const field = document.getElementById(prefixes[j] + fieldIDs[i]);
const field = document.getElementById(fieldIDs[i]);
field.textContent = '';
for (let n = 0; n <= this._count; n++) {
const opt = document.createElement("option") as HTMLOptionElement;
@ -548,7 +510,6 @@ export class createInvite {
}
}
}
}
get label(): string { return this._label.value; }
set label(label: string) { this._label.value = label; }
@ -619,41 +580,6 @@ export class createInvite {
this._minutes.value = ""+n;
this._checkDurationValidity();
}
get userExpiry(): boolean {
return this._userExpiryToggle.checked;
}
set userExpiry(enabled: boolean) {
this._userExpiryToggle.checked = enabled;
const parent = this._userExpiryToggle.parentElement;
if (enabled) {
parent.classList.add("~urge");
parent.classList.remove("~neutral");
} else {
parent.classList.add("~neutral");
parent.classList.remove("~urge");
}
this._userDays.disabled = !enabled;
this._userHours.disabled = !enabled;
this._userMinutes.disabled = !enabled;
}
get userDays(): number {
return +this._userDays.value;
}
set userDays(n: number) {
this._userDays.value = ""+n;
}
get userHours(): number {
return +this._userHours.value;
}
set userHours(n: number) {
this._userHours.value = ""+n;
}
get userMinutes(): number {
return +this._userMinutes.value;
}
set userMinutes(n: number) {
this._userMinutes.value = ""+n;
}
get sendTo(): string { return this._sendTo.value; }
set sendTo(address: string) { this._sendTo.value = address; }
@ -687,18 +613,10 @@ export class createInvite {
create = () => {
toggleLoader(this._createButton);
let userExpiry = this.userExpiry;
if (this.userDays == 0 && this.userHours == 0 && this.userMinutes == 0) {
userExpiry = false;
}
let send = {
"days": this.days,
"hours": this.hours,
"minutes": this.minutes,
"user-expiry": userExpiry,
"user-days": this.userDays,
"user-hours": this.userHours,
"user-minutes": this.userMinutes,
"multiple-uses": (this.uses > 1 || this.infiniteUses),
"no-limit": this.infiniteUses,
"remaining-uses": this.uses,
@ -724,42 +642,12 @@ export class createInvite {
this._infUses.onchange = () => { this.infiniteUses = this.infiniteUses; };
this.infiniteUses = false;
this._sendToEnabled.onchange = () => { this.sendToEnabled = this.sendToEnabled; };
this.userExpiry = false;
this._userExpiryToggle.onchange = () => { this.userExpiry = this._userExpiryToggle.checked; }
this._userDays.disabled = true;
this._userHours.disabled = true;
this._userMinutes.disabled = true;
this.sendToEnabled = false;
this._createButton.onclick = this.create;
this.sendTo = "";
this.uses = 1;
this.label = "";
const checkDuration = () => {
const invSpan = this._invDurationButton.nextElementSibling as HTMLSpanElement;
const userSpan = this._userExpiryButton.nextElementSibling as HTMLSpanElement;
if (this._invDurationButton.checked) {
this._invDuration.classList.remove("unfocused");
this._userExpiry.classList.add("unfocused");
invSpan.classList.add("!high");
invSpan.classList.remove("!normal");
userSpan.classList.add("!normal");
userSpan.classList.remove("!high");
} else if (this._userExpiryButton.checked) {
this._userExpiry.classList.remove("unfocused");
this._invDuration.classList.add("unfocused");
invSpan.classList.add("!normal");
invSpan.classList.remove("!high");
userSpan.classList.add("!high");
userSpan.classList.remove("!normal");
}
};
this._userExpiryButton.checked = false;
this._invDurationButton.checked = true;
this._userExpiryButton.onchange = checkDuration;
this._invDurationButton.onchange = checkDuration;
this._days.onchange = this._checkDurationValidity;
this._hours.onchange = this._checkDurationValidity;
this._minutes.onchange = this._checkDurationValidity;

View File

@ -694,7 +694,6 @@ class EmailEditor {
private _form = document.getElementById("form-editor") as HTMLFormElement;
private _header = document.getElementById("header-editor") as HTMLSpanElement;
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 _preview = document.getElementById("editor-preview") as HTMLDivElement;
private _previewContent: HTMLElement;
@ -746,11 +745,6 @@ class EmailEditor {
let ci = i % colors.length;
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
const buttons = this._variables.querySelectorAll("span.button") as NodeListOf<HTMLSpanElement>;
for (let i = 0; i < this._templ.variables.length; i++) {
@ -767,13 +761,11 @@ class EmailEditor {
}
loadPreview = () => {
let content = this._textArea.value;
if (this._templ.variables) {
for (let variable of this._templ.variables) {
let value = this._templ.values[variable.slice(1, -1)];
if (value === undefined) { value = variable; }
content = content.replace(new RegExp(variable, "g"), value);
}
}
if (this._templ.html == "") {
content = stripMarkdown(content);
this._previewContent.textContent = content;
@ -793,7 +785,7 @@ class EmailEditor {
}
showList = () => {
_get("/config/emails?lang=" + window.language, null, (req: XMLHttpRequest) => {
_get("/config/emails", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status != 200) {
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));

View File

@ -77,7 +77,6 @@ declare interface Modals {
announce: Modal;
editor: Modal;
customizeEmails: Modal;
extendExpiry: Modal;
}
interface Invite {
@ -91,8 +90,6 @@ interface Invite {
notifyCreation?: boolean;
profile?: string;
label?: string;
userExpiry?: boolean;
userExpiryTime?: string;
}
interface inviteList {

View File

@ -1,113 +0,0 @@
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)
}
}

View File

@ -81,7 +81,6 @@ func (app *appContext) AdminPage(gc *gin.Context) {
"strings": app.storage.lang.Admin[lang].Strings,
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
"language": app.storage.lang.Admin[lang].JSON,
"langName": lang,
})
}
@ -183,11 +182,6 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"notifications": app.storage.lang.Form[lang].notificationsJSON,
"code": code,
"confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false),
"userExpiry": inv.UserExpiry,
"userExpiryDays": inv.UserDays,
"userExpiryHours": inv.UserHours,
"userExpiryMinutes": inv.UserMinutes,
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
})
}