mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
userpage: change email (+ confirmation)
edit/add button added for email address. Confirmation works too.
This commit is contained in:
parent
03f1a3dbc0
commit
609039baeb
156
api-userpage.go
156
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
|
||||
}
|
||||
|
10
email.go
10
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 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, key)
|
||||
inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, url.PathEscape(key))
|
||||
}
|
||||
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
|
||||
template["confirmationURL"] = inviteLink
|
||||
template["message"] = message
|
||||
|
@ -17,6 +17,22 @@
|
||||
<title>{{ .lang.Strings.pageTitle }}</title>
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="modal-email" class="modal">
|
||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||
<div class="content">
|
||||
<span class="heading mb-4 my-2"></span>
|
||||
<label class="label supra row m-1" for="modal-email-input">{{ .strings.emailAddress }}</label>
|
||||
<div class="row">
|
||||
<input type="email" class="col sm field ~neutral @low input" id="modal-email-input" placeholder="{{ .strings.emailAddress }}">
|
||||
</div>
|
||||
<button class="button ~urge @low supra full-width center lg my-2 modal-submit">{{ .strings.submit }}</button>
|
||||
</div>
|
||||
<div class="confirmation-required unfocused">
|
||||
<span class="heading mb-4">{{ .strings.confirmationRequired }} <span class="modal-close">×</span></span>
|
||||
<p class="content mb-4">{{ .strings.confirmationRequiredMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="notification-box"></div>
|
||||
<div class="top-4 left-4 absolute">
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
@ -54,7 +70,7 @@
|
||||
</div>
|
||||
<div class="card @low dark:~d_neutral flex-col" id="card-contact">
|
||||
<span class="heading mb-2">{{ .strings.contactMethods }}</span>
|
||||
<div class="content flex justify-between flex-col"></div>
|
||||
<div class="content flex justify-between flex-col h-100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -66,7 +66,6 @@
|
||||
"variables": "Μεταβλητές",
|
||||
"preview": "Προεπισκόπηση",
|
||||
"reset": "Επαναφορά",
|
||||
"edit": "Επεξεργασία",
|
||||
"customizeMessages": "Παραμετροποίηση Emails",
|
||||
"advancedSettings": "Προχωρημένες Ρυθμίσεις",
|
||||
"customizeMessagesDescription": "Αν δεν θέλετε να ζρησιμοποιήσετε τα πρότυπα email του jfa-go, μπορείτε να δημιουργήσετε τα δικά σας με χρήση Markdown.",
|
||||
|
@ -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.",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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.",
|
||||
|
@ -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": "",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "",
|
||||
|
@ -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",
|
||||
|
@ -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.",
|
||||
|
@ -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:",
|
||||
|
@ -18,7 +18,6 @@
|
||||
"create": "创建",
|
||||
"apply": "申请",
|
||||
"delete": "删除",
|
||||
"add": "添加",
|
||||
"select": "选择",
|
||||
"name": "名称",
|
||||
"date": "日期",
|
||||
@ -47,7 +46,6 @@
|
||||
"conditionals": "条件性条款",
|
||||
"preview": "预览",
|
||||
"reset": "重设",
|
||||
"edit": "编辑",
|
||||
"donate": "捐助",
|
||||
"contactThrough": "联系方式:",
|
||||
"extendExpiry": "延长有效期",
|
||||
|
@ -18,7 +18,6 @@
|
||||
"create": "創建",
|
||||
"apply": "應用",
|
||||
"delete": "刪除",
|
||||
"add": "添加",
|
||||
"select": "選擇",
|
||||
"name": "帳戶名稱",
|
||||
"date": "日期",
|
||||
@ -49,7 +48,6 @@
|
||||
"conditionals": "條件",
|
||||
"preview": "預覽",
|
||||
"reset": "重設",
|
||||
"edit": "編輯",
|
||||
"donate": "捐贈",
|
||||
"sendPWR": "發送密碼重置",
|
||||
"contactThrough": "聯繫方式:",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -23,7 +23,8 @@
|
||||
"disabled": "Απενεργοποιημένο",
|
||||
"reEnable": "Επανα-ενεργοποίηση",
|
||||
"disable": "Απενεργοποίηση",
|
||||
"expiry": "Λήξη"
|
||||
"expiry": "Λήξη",
|
||||
"edit": "Επεξεργασία"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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": {}
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -32,7 +32,9 @@
|
||||
"disabled": "已禁用",
|
||||
"reEnable": "重新启用",
|
||||
"disable": "禁用",
|
||||
"expiry": "到期"
|
||||
"expiry": "到期",
|
||||
"add": "添加",
|
||||
"edit": "编辑"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "用户名/密码留空。",
|
||||
|
@ -32,7 +32,9 @@
|
||||
"disabled": "已禁用",
|
||||
"reEnable": "重新啟用",
|
||||
"disable": "禁用",
|
||||
"expiry": "到期"
|
||||
"expiry": "到期",
|
||||
"add": "添加",
|
||||
"edit": "編輯"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "帳戶名稱和/或密碼留空。",
|
||||
|
11
models.go
11
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
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,9 @@
|
||||
"contactMethods": "common",
|
||||
"accountStatus": "common",
|
||||
"notSet": "common",
|
||||
"expiry": "admin"
|
||||
"expiry": "common",
|
||||
"add": "admin",
|
||||
"edit": "admin"
|
||||
},
|
||||
"notifications": {
|
||||
"errorLoginBlank": "common",
|
||||
|
@ -111,6 +111,7 @@ declare interface Modals {
|
||||
matrix: Modal;
|
||||
sendPWR?: Modal;
|
||||
logs: Modal;
|
||||
email?: Modal;
|
||||
}
|
||||
|
||||
interface Invite {
|
||||
|
76
ts/user.ts
76
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 = `
|
||||
<div class="inline align-middle">
|
||||
<span class="shield ~info" alt="${name}">
|
||||
<span class="shield ~urge" alt="${name}">
|
||||
<span class="icon">
|
||||
${icon}
|
||||
</span>
|
||||
@ -86,13 +87,25 @@ class ContactMethods {
|
||||
<span class="ml-2 font-bold">${(details.value == "") ? window.lang.strings("notSet") : details.value}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<button class="user-contact-enabled-disabled button ~neutral">
|
||||
<button class="user-contact-enabled-disabled button ~neutral" ${details.value =="" ? "disabled" : ""}>
|
||||
<input type="checkbox" class="mr-2">
|
||||
<span>${window.lang.strings("enabled")}</span>
|
||||
</button>
|
||||
`;
|
||||
if (addEditFunc) {
|
||||
innerHTML += `
|
||||
<button class="user-contact-edit button ~info ml-2">
|
||||
<i class="ri-${details.value == "" ? "add" : "edit"}-fill mr-2"></i>
|
||||
<span>${details.value == "" ? window.lang.strings("add") : window.lang.strings("edit")}</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
innerHTML += `
|
||||
</div>
|
||||
`;
|
||||
|
||||
row.innerHTML = innerHTML;
|
||||
|
||||
this._buttons[name] = {
|
||||
element: row,
|
||||
details: details
|
||||
@ -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")) + `<span class="modal-close">×</span>`;
|
||||
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", `<i class="ri-mail-fill"></i>`],
|
||||
["discord", `<i class="ri-discord-fill"></i>`],
|
||||
["telegram", `<i class="ri-telegram-fill"></i>`],
|
||||
["matrix", `[m]`]
|
||||
const contactMethods: { name: string, icon: string, f: (add: boolean) => void }[] = [
|
||||
{name: "email", icon: `<i class="ri-mail-fill ri-lg"></i>`, f: addEditEmail},
|
||||
{name: "discord", icon: `<i class="ri-discord-fill ri-lg"></i>`, f: null},
|
||||
{name: "telegram", icon: `<i class="ri-telegram-fill ri-lg"></i>`, f: null},
|
||||
{name: "matrix", icon: `<span class="font-bold">[m]</span>`, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user