diff --git a/api-userpage.go b/api-userpage.go
index f29a3d7..dbfd0d5 100644
--- a/api-userpage.go
+++ b/api-userpage.go
@@ -1,6 +1,14 @@
package main
-import "github.com/gin-gonic/gin"
+import (
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/golang-jwt/jwt"
+)
// @Summary Returns the logged-in user's Jellyfin ID & Username.
// @Produce json
@@ -104,3 +112,149 @@ func (app *appContext) LogoutUser(gc *gin.Context) {
gc.SetCookie("refresh", "invalid", -1, "/my", gc.Request.URL.Hostname(), true, true)
respondBool(200, true, gc)
}
+
+// @Summary confirm an action (e.g. changing an email address.)
+// @Produce json
+// @Param jwt path string true "jwt confirmation code"
+// @Router /my/confirm/{jwt} [post]
+// @Success 200 {object} boolResponse
+// @Failure 400 {object} stringResponse
+// @Failure 404
+// @Success 303
+// @Failure 500 {object} stringResponse
+// @tags User Page
+func (app *appContext) ConfirmMyAction(gc *gin.Context) {
+ app.confirmMyAction(gc, "")
+}
+
+func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
+ var claims jwt.MapClaims
+ var target ConfirmationTarget
+ var id string
+ fail := func() {
+ gcHTML(gc, 404, "404.html", gin.H{
+ "cssClass": app.cssClass,
+ "cssVersion": cssVersion,
+ "contactMessage": app.config.Section("ui").Key("contact_message").String(),
+ })
+ }
+
+ // Validate key
+ if key == "" {
+ key = gc.Param("jwt")
+ }
+ token, err := jwt.Parse(key, checkToken)
+ if err != nil {
+ app.err.Printf("Failed to parse key: %s", err)
+ fail()
+ // respond(500, "unknownError", gc)
+ return
+ }
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ app.err.Printf("Failed to parse key: %s", err)
+ fail()
+ // respond(500, "unknownError", gc)
+ return
+ }
+ expiry := time.Unix(int64(claims["exp"].(float64)), 0)
+ if !(ok && token.Valid && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
+ app.err.Printf("Invalid key")
+ fail()
+ // respond(400, "invalidKey", gc)
+ return
+ }
+ target = ConfirmationTarget(int(claims["target"].(float64)))
+ id = claims["id"].(string)
+
+ // Perform an Action
+ if target == NoOp {
+ gc.Redirect(http.StatusSeeOther, "/my/account")
+ return
+ } else if target == UserEmailChange {
+ emailStore, ok := app.storage.emails[id]
+ if !ok {
+ emailStore = EmailAddress{
+ Contact: true,
+ }
+ }
+ emailStore.Addr = claims["email"].(string)
+ app.storage.emails[id] = emailStore
+ if app.config.Section("ombi").Key("enabled").MustBool(false) {
+ ombiUser, code, err := app.getOmbiUser(id)
+ if code == 200 && err == nil {
+ ombiUser["emailAddress"] = claims["email"].(string)
+ code, err = app.ombi.ModifyUser(ombiUser)
+ if code != 200 || err != nil {
+ app.err.Printf("%s: Failed to change ombi email address (%d): %v", ombiUser["userName"].(string), code, err)
+ }
+ }
+ }
+
+ app.storage.storeEmails()
+ app.info.Println("Email list modified")
+ gc.Redirect(http.StatusSeeOther, "/my/account")
+ return
+ }
+}
+
+// @Summary Modify your email address.
+// @Produce json
+// @Param ModifyMyEmailDTO body ModifyMyEmailDTO true "New email address."
+// @Success 200 {object} boolResponse
+// @Failure 400 {object} stringResponse
+// @Failure 401 {object} stringResponse
+// @Failure 500 {object} stringResponse
+// @Router /my/email [post]
+// @Security Bearer
+// @tags Users
+func (app *appContext) ModifyMyEmail(gc *gin.Context) {
+ var req ModifyMyEmailDTO
+ gc.BindJSON(&req)
+ app.debug.Println("Email modification requested")
+ if !strings.ContainsRune(req.Email, '@') {
+ respond(400, "Invalid Email Address", gc)
+ return
+ }
+ id := gc.GetString("jfId")
+
+ // We'll use the ConfirmMyAction route to do the work, even if we don't need to confirm the address.
+ claims := jwt.MapClaims{
+ "valid": true,
+ "id": id,
+ "email": req.Email,
+ "type": "confirmation",
+ "target": UserEmailChange,
+ "exp": time.Now().Add(time.Hour).Unix(),
+ }
+ tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
+
+ if err != nil {
+ app.err.Printf("Failed to generate confirmation token: %v", err)
+ respond(500, "errorUnknown", gc)
+ return
+ }
+
+ if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) {
+ user, status, err := app.jf.UserByID(id, false)
+ name := ""
+ if status == 200 && err == nil {
+ name = user.Name
+ }
+ app.debug.Printf("%s: Email confirmation required", id)
+ respond(401, "confirmEmail", gc)
+ msg, err := app.email.constructConfirmation("", name, key, app, false)
+ if err != nil {
+ app.err.Printf("%s: Failed to construct confirmation email: %v", name, err)
+ } else if err := app.email.send(msg, req.Email); err != nil {
+ app.err.Printf("%s: Failed to send user confirmation email: %v", name, err)
+ } else {
+ app.info.Printf("%s: Sent user confirmation email to \"%s\"", name, req.Email)
+ }
+ return
+ }
+
+ app.confirmMyAction(gc, key)
+ return
+}
diff --git a/email.go b/email.go
index 03a30b2..f31da18 100644
--- a/email.go
+++ b/email.go
@@ -10,6 +10,7 @@ import (
"html/template"
"io"
"io/fs"
+ "net/url"
"os"
"strconv"
"strings"
@@ -304,10 +305,17 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC
} else {
message := app.config.Section("messages").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
- if !strings.HasSuffix(inviteLink, "/invite") {
- inviteLink += "/invite"
+ if code == "" { // Personal email change
+ if strings.HasSuffix(inviteLink, "/invite") {
+ inviteLink = strings.TrimSuffix(inviteLink, "/invite")
+ }
+ inviteLink = fmt.Sprintf("%s/my/confirm/%s", inviteLink, url.PathEscape(key))
+ } else { // Invite email confirmation
+ if !strings.HasSuffix(inviteLink, "/invite") {
+ inviteLink += "/invite"
+ }
+ inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, url.PathEscape(key))
}
- inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, key)
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
template["confirmationURL"] = inviteLink
template["message"] = message
diff --git a/html/user.html b/html/user.html
index 6fda5a0..39789cd 100644
--- a/html/user.html
+++ b/html/user.html
@@ -17,6 +17,22 @@
{{ .lang.Strings.pageTitle }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ .strings.confirmationRequired }} ×
+
{{ .strings.confirmationRequiredMessage }}
+
+
+
@@ -54,7 +70,7 @@
diff --git a/lang/admin/da-dk.json b/lang/admin/da-dk.json
index ce1de62..3b117ce 100644
--- a/lang/admin/da-dk.json
+++ b/lang/admin/da-dk.json
@@ -18,7 +18,6 @@
"create": "Opret",
"apply": "Anvend",
"delete": "Slet",
- "add": "Tilføj",
"select": "Vælg",
"name": "Navn",
"date": "Dato",
@@ -46,7 +45,6 @@
"conditionals": "Betingelser",
"preview": "Eksempel",
"reset": "Nulstil",
- "edit": "Rediger",
"donate": "Doner",
"contactThrough": "Kontakt gennem:",
"extendExpiry": "Forlæng udløb",
diff --git a/lang/admin/de-de.json b/lang/admin/de-de.json
index 1c4de83..1004d5c 100644
--- a/lang/admin/de-de.json
+++ b/lang/admin/de-de.json
@@ -66,7 +66,6 @@
"variables": "Variablen",
"preview": "Vorschau",
"reset": "Zurücksetzen",
- "edit": "Bearbeiten",
"customizeMessages": "Benachrichtigungen anpassen",
"customizeMessagesDescription": "Wenn du jfa-go's E-Mail-Vorlagen nicht benutzen willst, kannst du deinen eigenen unter Verwendung von Markdown erstellen.",
"announce": "Ankündigen",
@@ -87,7 +86,6 @@
"contactThrough": "Kontakt über:",
"sendPIN": "Bitte den Benutzer, die unten stehende PIN an den Bot zu senden.",
"inviteMonths": "Monate",
- "add": "Hinzufügen",
"select": "Auswählen",
"searchDiscordUser": "Gib den Discord-Benutzername ein, um den Benutzer zu finden.",
"findDiscordUser": "Suche Discord-Benutzer",
diff --git a/lang/admin/el-gr.json b/lang/admin/el-gr.json
index 3b97023..dc358df 100644
--- a/lang/admin/el-gr.json
+++ b/lang/admin/el-gr.json
@@ -66,7 +66,6 @@
"variables": "Μεταβλητές",
"preview": "Προεπισκόπηση",
"reset": "Επαναφορά",
- "edit": "Επεξεργασία",
"customizeMessages": "Παραμετροποίηση Emails",
"advancedSettings": "Προχωρημένες Ρυθμίσεις",
"customizeMessagesDescription": "Αν δεν θέλετε να ζρησιμοποιήσετε τα πρότυπα email του jfa-go, μπορείτε να δημιουργήσετε τα δικά σας με χρήση Markdown.",
diff --git a/lang/admin/en-gb.json b/lang/admin/en-gb.json
index 01cca19..44a3cf8 100644
--- a/lang/admin/en-gb.json
+++ b/lang/admin/en-gb.json
@@ -93,12 +93,10 @@
"contactThrough": "Contact through:",
"select": "Select",
"date": "Date",
- "edit": "Edit",
"extendExpiry": "Extend expiry",
"sendPWR": "Send Password Reset",
"inviteMonths": "Months",
"inviteDuration": "Invite Duration",
- "add": "Add",
"update": "Update",
"user": "User",
"userExpiryDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.",
diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json
index 7174ea8..c1eb7e4 100644
--- a/lang/admin/en-us.json
+++ b/lang/admin/en-us.json
@@ -18,7 +18,6 @@
"create": "Create",
"apply": "Apply",
"delete": "Delete",
- "add": "Add",
"select": "Select",
"name": "Name",
"date": "Date",
@@ -51,7 +50,6 @@
"conditionals": "Conditionals",
"preview": "Preview",
"reset": "Reset",
- "edit": "Edit",
"donate": "Donate",
"unlink": "Unlink Account",
"sendPWR": "Send Password Reset",
diff --git a/lang/admin/es-es.json b/lang/admin/es-es.json
index 3a57fb3..6e41364 100644
--- a/lang/admin/es-es.json
+++ b/lang/admin/es-es.json
@@ -43,7 +43,6 @@
"variables": "Variables",
"preview": "Vista previa",
"reset": "Reiniciar",
- "edit": "Editar",
"extendExpiry": "Extender el vencimiento",
"customizeMessages": "Personalizar mensajes",
"customizeMessagesDescription": "Si no desea utilizar las plantillas de mensajes de jfa-go, puede crear las suyas con Markdown.",
@@ -85,7 +84,6 @@
"notifyUserCreation": "Sobre la creación de usuarios",
"conditionals": "Condicionales",
"donate": "Donar",
- "add": "Agregar",
"templates": "Plantillas",
"contactThrough": "Contactar a través de:",
"select": "Seleccionar",
diff --git a/lang/admin/fr-fr.json b/lang/admin/fr-fr.json
index c7937d0..2a5809e 100644
--- a/lang/admin/fr-fr.json
+++ b/lang/admin/fr-fr.json
@@ -73,7 +73,6 @@
"variables": "Variables",
"preview": "Aperçu",
"reset": "Réinitialisation",
- "edit": "Éditer",
"customizeMessages": "Personnaliser les e-mails",
"inviteDuration": "Durée de l'invitation",
"advancedSettings": "Paramètres avancés",
@@ -88,7 +87,6 @@
"extendExpiry": "Prolonger l'expiration",
"contactThrough": "Contacté par :",
"sendPIN": "Demandez à l'utilisateur d'envoyer le code PIN ci-dessous au bot.",
- "add": "Ajouter",
"select": "Sélectionner",
"findDiscordUser": "Trouver l'utilisateur Discord",
"linkMatrixDescription": "Entrez le nom d'utilisateur et le mot de passe de l'utilisateur pour l’utilisateur comme bot. Une fois soumis, l'application va redémarrer.",
diff --git a/lang/admin/hu-hu.json b/lang/admin/hu-hu.json
index d6e5470..9ad59ed 100644
--- a/lang/admin/hu-hu.json
+++ b/lang/admin/hu-hu.json
@@ -18,7 +18,6 @@
"create": "Létrehozás",
"apply": "Alkalmaz",
"delete": "Törlés",
- "add": "Hozzáadás",
"select": "Kiválasztás",
"name": "Név",
"date": "Dátum",
@@ -49,7 +48,6 @@
"conditionals": "Feltételek",
"preview": "Előnézet",
"reset": "Visszaállítás",
- "edit": "Szerkesztés",
"donate": "Támogatás",
"sendPWR": "Jelszó visszaállítás küldése",
"contactThrough": "",
diff --git a/lang/admin/id-id.json b/lang/admin/id-id.json
index 6161673..71a1869 100644
--- a/lang/admin/id-id.json
+++ b/lang/admin/id-id.json
@@ -66,7 +66,6 @@
"variables": "Variabel",
"preview": "Pratinjau",
"reset": "Setel ulang",
- "edit": "Edit",
"customizeMessages": "Sesuaikan Email",
"customizeMessagesDescription": "Jika Anda tidak ingin menggunakan templat email jfa-go, Anda dapat membuatnya sendiri menggunakan Markdown.",
"announce": "Mengumumkan",
diff --git a/lang/admin/nl-nl.json b/lang/admin/nl-nl.json
index 910202c..0464d96 100644
--- a/lang/admin/nl-nl.json
+++ b/lang/admin/nl-nl.json
@@ -71,7 +71,6 @@
"customizeMessagesDescription": "Als je de e-mailsjablonen van jfa-go niet wilt gebruiken, kun je met gebruik van Markdown je eigen aanmaken.",
"preview": "Voorbeeld",
"reset": "Reset",
- "edit": "Bewerken",
"customizeMessages": "E-mails aanpassen",
"inviteDuration": "Geldigheidsduur uitnodiging",
"userExpiryDescription": "Een bepaalde tijd na elke aanmelding, wordt de account verwijderd/uitgeschakeld door jfa-go. Dit kan aangepast worden in de instellingen.",
@@ -87,7 +86,6 @@
"donate": "Doneer",
"contactThrough": "Stuur bericht via:",
"sendPIN": "Vraag de gebruiker om onderstaande pincode naar de bot te sturen.",
- "add": "Voeg toe",
"searchDiscordUser": "Begin de Discord gebruikersnaam te typen om de gebruiker te vinden.",
"linkMatrixDescription": "Vul de gebruikersnaam en wachtwoord in van de gebruiker om als bot te gebruiken. De app start zodra ze zijn verstuurd.",
"select": "Selecteer",
diff --git a/lang/admin/pl-pl.json b/lang/admin/pl-pl.json
index b3957de..522fb02 100644
--- a/lang/admin/pl-pl.json
+++ b/lang/admin/pl-pl.json
@@ -18,7 +18,6 @@
"create": "",
"apply": "",
"delete": "",
- "add": "",
"select": "",
"name": "Imię",
"date": "Data",
@@ -49,7 +48,6 @@
"conditionals": "",
"preview": "",
"reset": "Zresetuj",
- "edit": "Edytuj",
"donate": "",
"sendPWR": "",
"contactThrough": "",
diff --git a/lang/admin/pt-br.json b/lang/admin/pt-br.json
index f84c2b9..2073940 100644
--- a/lang/admin/pt-br.json
+++ b/lang/admin/pt-br.json
@@ -71,7 +71,6 @@
"variables": "Variáveis",
"preview": "Pre-visualizar",
"reset": "Redefinir",
- "edit": "Editar",
"customizeMessages": "Customizar Emails",
"userExpiryDescription": "Após um determinado período de tempo de cada inscrição, o jfa-go apagará/desabilitará a conta. Você pode alterar essa opção nas configurações.",
"inviteDuration": "Duração do Convite",
@@ -89,7 +88,6 @@
"sendPIN": "Peça que o usuário envie o PIN abaixo para o bot.",
"searchDiscordUser": "Digite o nome de usuário do Discord.",
"findDiscordUser": "Encontrar usuário Discord",
- "add": "Adicionar",
"linkMatrixDescription": "Digite o nome de usuário e a senha para usar como bot. Depois de enviado, o aplicativo será reiniciado.",
"select": "Selecionar",
"templates": "Modelos",
diff --git a/lang/admin/sv-se.json b/lang/admin/sv-se.json
index f57d37e..60dd719 100644
--- a/lang/admin/sv-se.json
+++ b/lang/admin/sv-se.json
@@ -34,7 +34,6 @@
"variables": "Variabler",
"preview": "Förhandsvisning",
"reset": "Återställ",
- "edit": "Redigera",
"customizeMessages": "Anpassa e-post",
"customizeMessagesDescription": "Om du inte vill använda jfa-go's e-postmallar, så kan du skapa dina egna med Markdown.",
"markdownSupported": "Markdown stöds.",
diff --git a/lang/admin/vi-vn.json b/lang/admin/vi-vn.json
index cb058f4..6366e28 100644
--- a/lang/admin/vi-vn.json
+++ b/lang/admin/vi-vn.json
@@ -18,7 +18,6 @@
"create": "Tạo mới",
"apply": "Áp dụng",
"delete": "Xóa",
- "add": "Thêm",
"select": "Chọn",
"name": "Tên",
"date": "Ngày",
@@ -48,7 +47,6 @@
"conditionals": "Điều kiện",
"preview": "Xem trước",
"reset": "Đặt lại",
- "edit": "Chỉnh sửa",
"donate": "Đóng góp",
"sendPWR": "Gửi Đặt lại Mật khẩu",
"contactThrough": "Liên lạc qua:",
diff --git a/lang/admin/zh-hans.json b/lang/admin/zh-hans.json
index 97561a2..98d050f 100644
--- a/lang/admin/zh-hans.json
+++ b/lang/admin/zh-hans.json
@@ -18,7 +18,6 @@
"create": "创建",
"apply": "申请",
"delete": "删除",
- "add": "添加",
"select": "选择",
"name": "名称",
"date": "日期",
@@ -47,7 +46,6 @@
"conditionals": "条件性条款",
"preview": "预览",
"reset": "重设",
- "edit": "编辑",
"donate": "捐助",
"contactThrough": "联系方式:",
"extendExpiry": "延长有效期",
diff --git a/lang/admin/zh-hant.json b/lang/admin/zh-hant.json
index a4b318f..d876316 100644
--- a/lang/admin/zh-hant.json
+++ b/lang/admin/zh-hant.json
@@ -18,7 +18,6 @@
"create": "創建",
"apply": "應用",
"delete": "刪除",
- "add": "添加",
"select": "選擇",
"name": "帳戶名稱",
"date": "日期",
@@ -49,7 +48,6 @@
"conditionals": "條件",
"preview": "預覽",
"reset": "重設",
- "edit": "編輯",
"donate": "捐贈",
"sendPWR": "發送密碼重置",
"contactThrough": "聯繫方式:",
diff --git a/lang/common/da-dk.json b/lang/common/da-dk.json
index 8155ca8..b2c3f3f 100644
--- a/lang/common/da-dk.json
+++ b/lang/common/da-dk.json
@@ -32,7 +32,9 @@
"disabled": "Deaktiveret",
"reEnable": "Genaktiver",
"disable": "Deaktiver",
- "expiry": "Udløb"
+ "expiry": "Udløb",
+ "add": "Tilføj",
+ "edit": "Rediger"
},
"notifications": {
"errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.",
diff --git a/lang/common/de-de.json b/lang/common/de-de.json
index 9b10ccd..339ce88 100644
--- a/lang/common/de-de.json
+++ b/lang/common/de-de.json
@@ -32,7 +32,9 @@
"disabled": "Deaktiviert",
"reEnable": "Wieder aktivieren",
"disable": "Deaktivieren",
- "expiry": "Ablaufdatum"
+ "expiry": "Ablaufdatum",
+ "add": "Hinzufügen",
+ "edit": "Bearbeiten"
},
"notifications": {
"errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.",
diff --git a/lang/common/el-gr.json b/lang/common/el-gr.json
index 1a1a0d6..7826ab7 100644
--- a/lang/common/el-gr.json
+++ b/lang/common/el-gr.json
@@ -23,7 +23,8 @@
"disabled": "Απενεργοποιημένο",
"reEnable": "Επανα-ενεργοποίηση",
"disable": "Απενεργοποίηση",
- "expiry": "Λήξη"
+ "expiry": "Λήξη",
+ "edit": "Επεξεργασία"
},
"notifications": {
"errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.",
diff --git a/lang/common/en-gb.json b/lang/common/en-gb.json
index 799d4bf..bb44536 100644
--- a/lang/common/en-gb.json
+++ b/lang/common/en-gb.json
@@ -32,7 +32,9 @@
"disabled": "Disabled",
"reEnable": "Re-enable",
"disable": "Disable",
- "expiry": "Expiry"
+ "expiry": "Expiry",
+ "add": "Add",
+ "edit": "Edit"
},
"notifications": {
"errorLoginBlank": "The username and/or password was left blank.",
diff --git a/lang/common/en-us.json b/lang/common/en-us.json
index de53a29..3102ea3 100644
--- a/lang/common/en-us.json
+++ b/lang/common/en-us.json
@@ -33,9 +33,13 @@
"reEnable": "Re-enable",
"disable": "Disable",
"contactMethods": "Contact Methods",
+ "addContactMethod": "Add Contact Method",
+ "editContactMethod": "Edit Contact Method",
"accountStatus": "Account Status",
"notSet": "Not set",
- "expiry": "Expiry"
+ "expiry": "Expiry",
+ "add": "Add",
+ "edit": "Edit"
},
"notifications": {
"errorLoginBlank": "The username and/or password were left blank.",
@@ -58,4 +62,4 @@
"plural": "{n} Days"
}
}
-}
\ No newline at end of file
+}
diff --git a/lang/common/es-es.json b/lang/common/es-es.json
index 261b22e..b446d47 100644
--- a/lang/common/es-es.json
+++ b/lang/common/es-es.json
@@ -32,7 +32,9 @@
"disabled": "Desactivado",
"reEnable": "Reactivar",
"disable": "Desactivar",
- "expiry": "Expiración"
+ "expiry": "Expiración",
+ "add": "Agregar",
+ "edit": "Editar"
},
"notifications": {
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",
diff --git a/lang/common/fr-fr.json b/lang/common/fr-fr.json
index 9a31d3d..193ef1f 100644
--- a/lang/common/fr-fr.json
+++ b/lang/common/fr-fr.json
@@ -32,7 +32,9 @@
"disabled": "Désactivé",
"reEnable": "Ré-activé",
"disable": "Désactivé",
- "expiry": "Expiration"
+ "expiry": "Expiration",
+ "add": "Ajouter",
+ "edit": "Éditer"
},
"notifications": {
"errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.",
diff --git a/lang/common/hu-hu.json b/lang/common/hu-hu.json
index 367dcec..c4323a9 100644
--- a/lang/common/hu-hu.json
+++ b/lang/common/hu-hu.json
@@ -10,7 +10,9 @@
"disabled": "Tiltva",
"reEnable": "Újra engedélyezés",
"disable": "Letiltás",
- "expiry": "Lejárat"
+ "expiry": "Lejárat",
+ "add": "Hozzáadás",
+ "edit": "Szerkesztés"
},
"notifications": {},
"quantityStrings": {}
diff --git a/lang/common/id-id.json b/lang/common/id-id.json
index b517b8d..a925667 100644
--- a/lang/common/id-id.json
+++ b/lang/common/id-id.json
@@ -17,7 +17,8 @@
"time12h": "Waktu 12 jam",
"theme": "Tema",
"login": "Masuk",
- "logout": "Keluar"
+ "logout": "Keluar",
+ "edit": "Edit"
},
"notifications": {
"errorLoginBlank": "Nama pengguna dan / atau sandi kosong.",
diff --git a/lang/common/nl-nl.json b/lang/common/nl-nl.json
index 9dbddee..b43329a 100644
--- a/lang/common/nl-nl.json
+++ b/lang/common/nl-nl.json
@@ -32,7 +32,9 @@
"disabled": "Uitgeschakeld",
"reEnable": "Opnieuw inschakelen",
"disable": "Uitschakelen",
- "expiry": "Verloop"
+ "expiry": "Verloop",
+ "add": "Voeg toe",
+ "edit": "Bewerken"
},
"notifications": {
"errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.",
diff --git a/lang/common/pl-pl.json b/lang/common/pl-pl.json
index 8d042fa..6dbf2bc 100644
--- a/lang/common/pl-pl.json
+++ b/lang/common/pl-pl.json
@@ -29,7 +29,8 @@
"enabled": "Włączone",
"disabled": "Wyłączone",
"disable": "Wyłączone",
- "expiry": "Wygasa"
+ "expiry": "Wygasa",
+ "edit": "Edytuj"
},
"notifications": {
"errorConnection": "Nie udało się połączyć z jfa-go.",
diff --git a/lang/common/pt-br.json b/lang/common/pt-br.json
index e4f6191..5ed9ae1 100644
--- a/lang/common/pt-br.json
+++ b/lang/common/pt-br.json
@@ -32,7 +32,9 @@
"disabled": "Desativado",
"reEnable": "Reativar",
"disable": "Desativar",
- "expiry": "Expira"
+ "expiry": "Expira",
+ "add": "Adicionar",
+ "edit": "Editar"
},
"notifications": {
"errorLoginBlank": "O nome de usuário e/ou senha foram deixados em branco.",
diff --git a/lang/common/sv-se.json b/lang/common/sv-se.json
index 25d5962..554ae2c 100644
--- a/lang/common/sv-se.json
+++ b/lang/common/sv-se.json
@@ -20,7 +20,8 @@
"admin": "Admin",
"enabled": "Aktiverad",
"disabled": "Inaktiverad",
- "expiry": "Löper ut"
+ "expiry": "Löper ut",
+ "edit": "Redigera"
},
"notifications": {
"errorLoginBlank": "Användarnamnet och/eller lösenordet lämnades tomt.",
diff --git a/lang/common/vi-vn.json b/lang/common/vi-vn.json
index 4464173..dce2ccb 100644
--- a/lang/common/vi-vn.json
+++ b/lang/common/vi-vn.json
@@ -10,7 +10,9 @@
"disabled": "Tắt",
"reEnable": "Mở lại",
"disable": "Tắt",
- "expiry": "Hết hạn"
+ "expiry": "Hết hạn",
+ "add": "Thêm",
+ "edit": "Chỉnh sửa"
},
"notifications": {
"errorConnection": "Không thể kết nối với jfa-go.",
diff --git a/lang/common/zh-hans.json b/lang/common/zh-hans.json
index 17e7354..1c6daa7 100644
--- a/lang/common/zh-hans.json
+++ b/lang/common/zh-hans.json
@@ -32,7 +32,9 @@
"disabled": "已禁用",
"reEnable": "重新启用",
"disable": "禁用",
- "expiry": "到期"
+ "expiry": "到期",
+ "add": "添加",
+ "edit": "编辑"
},
"notifications": {
"errorLoginBlank": "用户名/密码留空。",
diff --git a/lang/common/zh-hant.json b/lang/common/zh-hant.json
index f82182c..a4fe3a6 100644
--- a/lang/common/zh-hant.json
+++ b/lang/common/zh-hant.json
@@ -32,7 +32,9 @@
"disabled": "已禁用",
"reEnable": "重新啟用",
"disable": "禁用",
- "expiry": "到期"
+ "expiry": "到期",
+ "add": "添加",
+ "edit": "編輯"
},
"notifications": {
"errorLoginBlank": "帳戶名稱和/或密碼留空。",
diff --git a/models.go b/models.go
index 136bcb0..084d010 100644
--- a/models.go
+++ b/models.go
@@ -391,3 +391,14 @@ type MyDetailsContactMethodsDTO struct {
Value string `json:"value"`
Enabled bool `json:"enabled"`
}
+
+type ModifyMyEmailDTO struct {
+ Email string `json:"email"`
+}
+
+type ConfirmationTarget int
+
+const (
+ UserEmailChange ConfirmationTarget = iota
+ NoOp
+)
diff --git a/router.go b/router.go
index 9e026e1..27e2281 100644
--- a/router.go
+++ b/router.go
@@ -147,6 +147,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
router.GET(p+"/my/account", app.MyUserPage)
router.GET(p+"/my/token/login", app.getUserTokenLogin)
router.GET(p+"/my/token/refresh", app.getUserTokenRefresh)
+ router.GET(p+"/my/confirm/:jwt", app.ConfirmMyAction)
}
}
if *SWAGGER {
@@ -229,6 +230,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
user.GET(p+"/details", app.MyDetails)
user.POST(p+"/contact", app.SetMyContactMethods)
user.POST(p+"/logout", app.LogoutUser)
+ user.POST(p+"/email", app.ModifyMyEmail)
}
}
}
diff --git a/scripts/langmover/common.json b/scripts/langmover/common.json
index 76eb632..b04d430 100644
--- a/scripts/langmover/common.json
+++ b/scripts/langmover/common.json
@@ -35,7 +35,9 @@
"contactMethods": "common",
"accountStatus": "common",
"notSet": "common",
- "expiry": "admin"
+ "expiry": "common",
+ "add": "admin",
+ "edit": "admin"
},
"notifications": {
"errorLoginBlank": "common",
diff --git a/ts/typings/d.ts b/ts/typings/d.ts
index 4096fb9..4c174da 100644
--- a/ts/typings/d.ts
+++ b/ts/typings/d.ts
@@ -111,6 +111,7 @@ declare interface Modals {
matrix: Modal;
sendPWR?: Modal;
logs: Modal;
+ email?: Modal;
}
interface Invite {
diff --git a/ts/user.ts b/ts/user.ts
index ca526e4..a611c15 100644
--- a/ts/user.ts
+++ b/ts/user.ts
@@ -1,7 +1,7 @@
import { ThemeManager } from "./modules/theme.js";
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { Modal } from "./modules/modal.js";
-import { _get, _post, notificationBox, whichAnimationEvent, toDateString } from "./modules/common.js";
+import { _get, _post, notificationBox, whichAnimationEvent, toDateString, toggleLoader } from "./modules/common.js";
import { Login } from "./modules/login.js";
interface userWindow extends Window {
@@ -25,6 +25,7 @@ window.modals = {} as Modals;
(() => {
window.modals.login = new Modal(document.getElementById("modal-login"), true);
+ window.modals.email = new Modal(document.getElementById("modal-email"), false);
})();
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
@@ -73,12 +74,12 @@ class ContactMethods {
this._buttons = {};
}
- append = (name: string, details: MyDetailsContactMethod, icon: string) => {
+ append = (name: string, details: MyDetailsContactMethod, icon: string, addEditFunc?: (add: boolean) => void) => {
const row = document.createElement("div");
row.classList.add("row", "flex-expand", "my-2");
- row.innerHTML = `
+ let innerHTML = `
-
+
${icon}
@@ -86,12 +87,24 @@ class ContactMethods {
${(details.value == "") ? window.lang.strings("notSet") : details.value}
-
`;
+
+ row.innerHTML = innerHTML;
this._buttons[name] = {
element: row,
@@ -102,11 +115,11 @@ class ContactMethods {
const checkbox = button.querySelector("input[type=checkbox]") as HTMLInputElement;
const setButtonAppearance = () => {
if (checkbox.checked) {
- button.classList.add("~info");
+ button.classList.add("~urge");
button.classList.remove("~neutral");
} else {
button.classList.add("~neutral");
- button.classList.remove("~info");
+ button.classList.remove("~urge");
}
};
const onPress = () => {
@@ -124,6 +137,11 @@ class ContactMethods {
checkbox.checked = details.enabled;
setButtonAppearance();
+ if (addEditFunc) {
+ const addEditButton = row.querySelector(".user-contact-edit") as HTMLButtonElement;
+ addEditButton.onclick = () => addEditFunc(details.value == "");
+ }
+
this._content.appendChild(row);
};
@@ -220,6 +238,36 @@ var expiryCard = new ExpiryCard(statusCard);
var contactMethodList = new ContactMethods(contactCard);
+const addEditEmail = (add: boolean): void => {
+ console.log("call");
+ const heading = window.modals.email.modal.querySelector(".heading");
+ heading.innerHTML = (add ? window.lang.strings("addContactMethod") : window.lang.strings("editContactMethod")) + `×`;
+ const input = document.getElementById("modal-email-input") as HTMLInputElement;
+ input.value = "";
+ const confirmationRequired = window.modals.email.modal.querySelector(".confirmation-required");
+ confirmationRequired.classList.add("unfocused");
+
+ const content = window.modals.email.modal.querySelector(".content");
+ content.classList.remove("unfocused");
+
+ const submit = window.modals.email.modal.querySelector(".modal-submit") as HTMLButtonElement;
+ submit.onclick = () => {
+ toggleLoader(submit);
+ _post("/my/email", {"email": input.value}, (req: XMLHttpRequest) => {
+ if (req.readyState == 4 && (req.status == 303 || req.status == 200)) {
+ window.location.reload();
+ }
+ }, true, (req: XMLHttpRequest) => {
+ if (req.readyState == 4 && req.status == 401) {
+ content.classList.add("unfocused");
+ confirmationRequired.classList.remove("unfocused");
+ }
+ });
+ }
+
+ window.modals.email.show();
+}
+
document.addEventListener("details-reload", () => {
_get("/my/details", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
@@ -244,16 +292,16 @@ document.addEventListener("details-reload", () => {
contactMethodList.clear();
- const contactMethods = [
- ["email", ``],
- ["discord", ``],
- ["telegram", ``],
- ["matrix", `[m]`]
+ const contactMethods: { name: string, icon: string, f: (add: boolean) => void }[] = [
+ {name: "email", icon: ``, f: addEditEmail},
+ {name: "discord", icon: ``, f: null},
+ {name: "telegram", icon: ``, f: null},
+ {name: "matrix", icon: `[m]`, f: null}
];
for (let method of contactMethods) {
- if (method[0] in details) {
- contactMethodList.append(method[0], details[method[0]], method[1]);
+ if (method.name in details) {
+ contactMethodList.append(method.name, details[method.name], method.icon, method.f);
}
}