From 609039baeb7fd370738543d4825fe666c17a78b3 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sun, 18 Jun 2023 19:38:09 +0100 Subject: [PATCH] userpage: change email (+ confirmation) edit/add button added for email address. Confirmation works too. --- api-userpage.go | 156 +++++++++++++++++++++++++++++++++- email.go | 14 ++- html/user.html | 18 +++- lang/admin/da-dk.json | 2 - lang/admin/de-de.json | 2 - lang/admin/el-gr.json | 1 - lang/admin/en-gb.json | 2 - lang/admin/en-us.json | 2 - lang/admin/es-es.json | 2 - lang/admin/fr-fr.json | 2 - lang/admin/hu-hu.json | 2 - lang/admin/id-id.json | 1 - lang/admin/nl-nl.json | 2 - lang/admin/pl-pl.json | 2 - lang/admin/pt-br.json | 2 - lang/admin/sv-se.json | 1 - lang/admin/vi-vn.json | 2 - lang/admin/zh-hans.json | 2 - lang/admin/zh-hant.json | 2 - lang/common/da-dk.json | 4 +- lang/common/de-de.json | 4 +- lang/common/el-gr.json | 3 +- lang/common/en-gb.json | 4 +- lang/common/en-us.json | 8 +- lang/common/es-es.json | 4 +- lang/common/fr-fr.json | 4 +- lang/common/hu-hu.json | 4 +- lang/common/id-id.json | 3 +- lang/common/nl-nl.json | 4 +- lang/common/pl-pl.json | 3 +- lang/common/pt-br.json | 4 +- lang/common/sv-se.json | 3 +- lang/common/vi-vn.json | 4 +- lang/common/zh-hans.json | 4 +- lang/common/zh-hant.json | 4 +- models.go | 11 +++ router.go | 2 + scripts/langmover/common.json | 4 +- ts/typings/d.ts | 1 + ts/user.ts | 76 ++++++++++++++--- 40 files changed, 309 insertions(+), 66 deletions(-) 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 }} +
@@ -54,7 +70,7 @@
{{ .strings.contactMethods }} -
+
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}
- + `; + if (addEditFunc) { + innerHTML += ` + + `; + } + innerHTML += `
`; + + 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); } }