mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-09 20:00:12 +00:00
Compare commits
6 Commits
8113f794ab
...
3b3f37365a
Author | SHA1 | Date | |
---|---|---|---|
3b3f37365a | |||
22c91be127 | |||
3ec3e9672e | |||
86daa70ccb | |||
db97c3b2d4 | |||
4f298bbc8c |
@ -477,3 +477,77 @@ func (app *appContext) UnlinkMyMatrix(gc *gin.Context) {
|
|||||||
app.storage.DeleteMatrixKey(gc.GetString("jfId"))
|
app.storage.DeleteMatrixKey(gc.GetString("jfId"))
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Generate & send a password reset link if the given username/email/contact method exists. Doesn't give you any info about it's success.
|
||||||
|
// @Produce json
|
||||||
|
// @Param address path string true "address/contact method associated w/ your account."
|
||||||
|
// @Success 204 {object} boolResponse
|
||||||
|
// @Failure 400 {object} boolResponse
|
||||||
|
// @Failure 500 {object} boolResponse
|
||||||
|
// @Router /my/password/reset/{address} [post]
|
||||||
|
// @tags Users
|
||||||
|
func (app *appContext) ResetMyPassword(gc *gin.Context) {
|
||||||
|
// All requests should take 1 second, to make it harder to tell if a success occured or not.
|
||||||
|
timerWait := make(chan bool)
|
||||||
|
cancel := time.AfterFunc(1*time.Second, func() {
|
||||||
|
timerWait <- true
|
||||||
|
})
|
||||||
|
address := gc.Param("address")
|
||||||
|
if address == "" {
|
||||||
|
app.debug.Println("Ignoring empty request for PWR")
|
||||||
|
cancel.Stop()
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pwr InternalPWR
|
||||||
|
var err error
|
||||||
|
|
||||||
|
jfID := app.ReverseUserSearch(address)
|
||||||
|
if jfID == "" {
|
||||||
|
app.debug.Printf("Ignoring PWR request: User not found")
|
||||||
|
|
||||||
|
for range timerWait {
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pwr, err = app.GenInternalReset(jfID)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to get user from Jellyfin: %v", err)
|
||||||
|
for range timerWait {
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if app.internalPWRs == nil {
|
||||||
|
app.internalPWRs = map[string]InternalPWR{}
|
||||||
|
}
|
||||||
|
app.internalPWRs[pwr.PIN] = pwr
|
||||||
|
// FIXME: Send to all contact methods
|
||||||
|
msg, err := app.email.constructReset(
|
||||||
|
PasswordReset{
|
||||||
|
Pin: pwr.PIN,
|
||||||
|
Username: pwr.Username,
|
||||||
|
Expiry: pwr.Expiry,
|
||||||
|
Internal: true,
|
||||||
|
}, app, false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to construct password reset message for \"%s\": %v", pwr.Username, err)
|
||||||
|
for range timerWait {
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if err := app.sendByID(msg, jfID); err != nil {
|
||||||
|
app.err.Printf("Failed to send password reset message to \"%s\": %v", address, err)
|
||||||
|
} else {
|
||||||
|
app.info.Printf("Sent password reset message to \"%s\"", address)
|
||||||
|
}
|
||||||
|
for range timerWait {
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -377,7 +377,7 @@
|
|||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
"name": "User Page",
|
"name": "User Page",
|
||||||
"description": "Settings for the user page, which provides useful info and tools to users directly.",
|
"description": "The User Page (My Account) allows users to access and modify info directly, such as changing/adding contact methods, seeing their expiry date, or changing their password. Password resets can also be initiated from here, given a contact method or username. ",
|
||||||
"depends_true": "ui|jellyfin_login"
|
"depends_true": "ui|jellyfin_login"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
@ -487,6 +487,15 @@ a:hover:not(.lang-link):not(.\~urge), a:active:not(.lang-link):not(.\~urge) {
|
|||||||
color: var(--color-urge-200);
|
color: var(--color-urge-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.button,
|
||||||
|
a.button:link,
|
||||||
|
a.button:visited,
|
||||||
|
a.button:focus,
|
||||||
|
a.buton:hover {
|
||||||
|
color: var(--color-content) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.link-center {
|
.link-center {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
81
email.go
81
email.go
@ -522,18 +522,6 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenResetLink generates and returns a password reset link.
|
|
||||||
func (app *appContext) GenResetLink(pin string) (string, error) {
|
|
||||||
url := app.config.Section("password_resets").Key("url_base").String()
|
|
||||||
var pinLink string
|
|
||||||
if url == "" {
|
|
||||||
return pinLink, fmt.Errorf("disabled as no URL Base provided. Set in Settings > Password Resets.")
|
|
||||||
}
|
|
||||||
// Strip /invite from end of this URL, ik it's ugly.
|
|
||||||
pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin)
|
|
||||||
return pinLink, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
|
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
|
||||||
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
||||||
message := app.config.Section("messages").Key("message").String()
|
message := app.config.Section("messages").Key("message").String()
|
||||||
@ -835,38 +823,37 @@ func (emailer *Emailer) send(email *Message, address ...string) error {
|
|||||||
return emailer.sender.Send(emailer.fromName, emailer.fromAddr, email, address...)
|
return emailer.sender.Send(emailer.fromName, emailer.fromAddr, email, address...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) sendByID(email *Message, ID ...string) error {
|
func (app *appContext) sendByID(email *Message, ID ...string) (err error) {
|
||||||
for _, id := range ID {
|
for _, id := range ID {
|
||||||
var err error
|
|
||||||
if tgChat, ok := app.storage.GetTelegramKey(id); ok && tgChat.Contact && telegramEnabled {
|
if tgChat, ok := app.storage.GetTelegramKey(id); ok && tgChat.Contact && telegramEnabled {
|
||||||
err = app.telegram.Send(email, tgChat.ChatID)
|
err = app.telegram.Send(email, tgChat.ChatID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
if dcChat, ok := app.storage.GetDiscordKey(id); ok && dcChat.Contact && discordEnabled {
|
if dcChat, ok := app.storage.GetDiscordKey(id); ok && dcChat.Contact && discordEnabled {
|
||||||
err = app.discord.Send(email, dcChat.ChannelID)
|
err = app.discord.Send(email, dcChat.ChannelID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
if mxChat, ok := app.storage.GetMatrixKey(id); ok && mxChat.Contact && matrixEnabled {
|
if mxChat, ok := app.storage.GetMatrixKey(id); ok && mxChat.Contact && matrixEnabled {
|
||||||
err = app.matrix.Send(email, mxChat)
|
err = app.matrix.Send(email, mxChat)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
if address, ok := app.storage.GetEmailsKey(id); ok && address.Contact && emailEnabled {
|
if address, ok := app.storage.GetEmailsKey(id); ok && address.Contact && emailEnabled {
|
||||||
err = app.email.send(email, address.Addr)
|
err = app.email.send(email, address.Addr)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) getAddressOrName(jfID string) string {
|
func (app *appContext) getAddressOrName(jfID string) string {
|
||||||
@ -879,5 +866,39 @@ func (app *appContext) getAddressOrName(jfID string) string {
|
|||||||
if addr, ok := app.storage.GetEmailsKey(jfID); ok {
|
if addr, ok := app.storage.GetEmailsKey(jfID); ok {
|
||||||
return addr.Addr
|
return addr.Addr
|
||||||
}
|
}
|
||||||
|
if mxChat, ok := app.storage.GetMatrixKey(jfID); ok && mxChat.Contact && matrixEnabled {
|
||||||
|
return mxChat.UserID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReverseUserSearch returns the jellyfin ID of the user with the given username, email, or contact method username.
|
||||||
|
// returns "" if none found. returns only the first match, might be an issue if there are users with the same contact method usernames.
|
||||||
|
func (app *appContext) ReverseUserSearch(address string) string {
|
||||||
|
user, status, err := app.jf.UserByName(address, false)
|
||||||
|
if status == 200 && err == nil {
|
||||||
|
return user.ID
|
||||||
|
}
|
||||||
|
for id, email := range app.storage.GetEmails() {
|
||||||
|
if strings.ToLower(address) == strings.ToLower(email.Addr) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id, dcUser := range app.storage.GetDiscord() {
|
||||||
|
if RenderDiscordUsername(dcUser) == strings.ToLower(address) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tgUsername := strings.TrimPrefix(address, "@")
|
||||||
|
for id, tgUser := range app.storage.GetTelegram() {
|
||||||
|
if tgUsername == tgUser.Username {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id, mxUser := range app.storage.GetMatrix() {
|
||||||
|
if address == mxUser.UserID {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -410,6 +410,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="top-4 right-4 absolute">
|
||||||
|
<a class="button ~info" href="/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||||
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<header class="flex flex-wrap items-center justify-between">
|
<header class="flex flex-wrap items-center justify-between">
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
window.captcha = {{ .captcha }};
|
window.captcha = {{ .captcha }};
|
||||||
window.reCAPTCHA = {{ .reCAPTCHA }};
|
window.reCAPTCHA = {{ .reCAPTCHA }};
|
||||||
window.reCAPTCHASiteKey = "{{ .reCAPTCHASiteKey }}";
|
window.reCAPTCHASiteKey = "{{ .reCAPTCHASiteKey }}";
|
||||||
|
window.userPageEnabled = {{ .userPageEnabled }};
|
||||||
</script>
|
</script>
|
||||||
{{ if .passwordReset }}
|
{{ if .passwordReset }}
|
||||||
<script src="js/pwr.js" type="module"></script>
|
<script src="js/pwr.js" type="module"></script>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
|
||||||
<span class="heading mb-4">{{ if .passwordReset }}{{ .strings.passwordReset }}{{ else }}{{ .strings.successHeader }}{{ end }}</span>
|
<span class="heading mb-4">{{ if .passwordReset }}{{ .strings.passwordReset }}{{ else }}{{ .strings.successHeader }}{{ end }}</span>
|
||||||
<p class="content mb-4">{{ if .passwordReset }}{{ .strings.youCanLoginPassword }}{{ else }}{{ .successMessage }}{{ end }}</p>
|
<p class="content mb-4">{{ if .passwordReset }}{{ .strings.youCanLoginPassword }}{{ else }}{{ .successMessage }}{{ end }}</p>
|
||||||
|
{{ if .userPageEnabled }}<p class="content mb-4" id="modal-success-user-page-area" my-account-term="{{ .strings.myAccount }}">{{ .strings.userPageSuccessMessage }}</p>{{ end }}
|
||||||
<a class="button ~urge @low full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
|
<a class="button ~urge @low full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,11 @@
|
|||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
|
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
|
||||||
|
{{ if index . "pwrEnabled" }}
|
||||||
|
{{ if .pwrEnabled }}
|
||||||
|
<span class="button ~info @low full-width center supra submit my-2" id="modal-login-pwr">{{ .strings.resetPassword }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
window.notificationsEnabled = {{ .notifications }};
|
window.notificationsEnabled = {{ .notifications }};
|
||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
window.ombiEnabled = {{ .ombiEnabled }};
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
|
window.pwrEnabled = {{ .pwrEnabled }};
|
||||||
window.linkResetEnabled = {{ .linkResetEnabled }};
|
window.linkResetEnabled = {{ .linkResetEnabled }};
|
||||||
window.language = "{{ .langName }}";
|
window.language = "{{ .langName }}";
|
||||||
window.telegramEnabled = {{ .telegramEnabled }};
|
window.telegramEnabled = {{ .telegramEnabled }};
|
||||||
@ -24,7 +25,7 @@
|
|||||||
window.matrixUserID = "{{ .matrixUser }}";
|
window.matrixUserID = "{{ .matrixUser }}";
|
||||||
</script>
|
</script>
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>{{ .lang.Strings.pageTitle }}</title>
|
<title>{{ .lang.Strings.myAccount }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="max-w-full overflow-x-hidden section">
|
<body class="max-w-full overflow-x-hidden section">
|
||||||
<div id="modal-email" class="modal">
|
<div id="modal-email" class="modal">
|
||||||
@ -43,6 +44,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if .pwrEnabled }}
|
||||||
|
<div id="modal-pwr" class="modal">
|
||||||
|
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~neutral @low">
|
||||||
|
<span class="heading">{{ .strings.resetPassword }}</span>
|
||||||
|
<p class="content my-2">
|
||||||
|
{{ if .linkResetEnabled }}
|
||||||
|
{{ .strings.resetPasswordThroughLink }}
|
||||||
|
{{ else }}
|
||||||
|
{{ .strings.resetPasswordThroughJellyfin }}
|
||||||
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" class="col sm field ~neutral @low input" id="pwr-address" placeholder="username | example@example.com | user#1234 | @user:host | @username">
|
||||||
|
</div>
|
||||||
|
{{ if .linkResetEnabled }}
|
||||||
|
<span class="button ~info @low full-width center mt-4" id="pwr-submit">
|
||||||
|
{{ .strings.submit }}
|
||||||
|
</span>
|
||||||
|
{{ else }}
|
||||||
|
<a class="button ~info @low full-width center mt-4" href="{{ .jfLink }}" target="_blank">{{ .strings.continue }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
{{ template "login-modal.html" . }}
|
{{ template "login-modal.html" . }}
|
||||||
{{ template "account-linking.html" . }}
|
{{ template "account-linking.html" . }}
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
@ -69,6 +94,9 @@
|
|||||||
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||||
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="top-4 right-4 absolute">
|
||||||
|
<a class="button ~info unfocused" href="/" id="admin-back-button"><i class="ri-arrow-left-fill mr-2"></i>{{ .strings.admin }}</a>
|
||||||
|
</div>
|
||||||
<div class="page-container unfocused">
|
<div class="page-container unfocused">
|
||||||
<div class="card @low dark:~d_neutral mb-4" id="card-user">
|
<div class="card @low dark:~d_neutral mb-4" id="card-user">
|
||||||
<span class="heading mb-2"></span>
|
<span class="heading mb-2"></span>
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
"expiry": "Expiry",
|
"expiry": "Expiry",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete"
|
"delete": "Delete",
|
||||||
|
"myAccount": "My Account"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"errorLoginBlank": "The username and/or password were left blank.",
|
"errorLoginBlank": "The username and/or password were left blank.",
|
||||||
|
@ -25,7 +25,13 @@
|
|||||||
"editContactMethod": "Edit Contact Method",
|
"editContactMethod": "Edit Contact Method",
|
||||||
"joinTheServer": "Join the server:",
|
"joinTheServer": "Join the server:",
|
||||||
"customMessagePlaceholderHeader": "Customize this card",
|
"customMessagePlaceholderHeader": "Customize this card",
|
||||||
"customMessagePlaceholderContent": "Click the user page edit button in settings to customize this card, or show one on the login screen, and don't worry, the user can't see this."
|
"customMessagePlaceholderContent": "Click the user page edit button in settings to customize this card, or show one on the login screen, and don't worry, the user can't see this.",
|
||||||
|
"userPageSuccessMessage": "You can see and change details about your account later on the {myAccount} page.",
|
||||||
|
"resetPassword": "Reset Password",
|
||||||
|
"resetPasswordThroughJellyfin": "To reset your password, visit {jfLink} and press the \"Forgot Password\" button.",
|
||||||
|
"resetPasswordThroughLink": "To reset your password, enter your username, email address or a linked contact method username, and submit. A link will be sent to reset your password.",
|
||||||
|
"resetSent": "Reset Sent.",
|
||||||
|
"resetSentDescription": "If an account with the given username/contact method exists, a password reset link has been sent via all contact methods available. The code will expire in 30 minutes."
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"errorUserExists": "User already exists.",
|
"errorUserExists": "User already exists.",
|
||||||
|
13
pwreset.go
13
pwreset.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -25,6 +26,18 @@ func (app *appContext) GenInternalReset(userID string) (InternalPWR, error) {
|
|||||||
return pwr, nil
|
return pwr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenResetLink generates and returns a password reset link.
|
||||||
|
func (app *appContext) GenResetLink(pin string) (string, error) {
|
||||||
|
url := app.config.Section("password_resets").Key("url_base").String()
|
||||||
|
var pinLink string
|
||||||
|
if url == "" {
|
||||||
|
return pinLink, fmt.Errorf("disabled as no URL Base provided. Set in Settings > Password Resets.")
|
||||||
|
}
|
||||||
|
// Strip /invite from end of this URL, ik it's ugly.
|
||||||
|
pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin)
|
||||||
|
return pinLink, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) StartPWR() {
|
func (app *appContext) StartPWR() {
|
||||||
app.info.Println("Starting password reset daemon")
|
app.info.Println("Starting password reset daemon")
|
||||||
path := app.config.Section("password_resets").Key("watch_directory").String()
|
path := app.config.Section("password_resets").Key("watch_directory").String()
|
||||||
|
@ -148,6 +148,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
router.GET(p+"/my/token/login", app.getUserTokenLogin)
|
router.GET(p+"/my/token/login", app.getUserTokenLogin)
|
||||||
router.GET(p+"/my/token/refresh", app.getUserTokenRefresh)
|
router.GET(p+"/my/token/refresh", app.getUserTokenRefresh)
|
||||||
router.GET(p+"/my/confirm/:jwt", app.ConfirmMyAction)
|
router.GET(p+"/my/confirm/:jwt", app.ConfirmMyAction)
|
||||||
|
router.POST(p+"/my/password/reset/:address", app.ResetMyPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *SWAGGER {
|
if *SWAGGER {
|
||||||
|
@ -35,6 +35,7 @@ interface formWindow extends Window {
|
|||||||
captcha: boolean;
|
captcha: boolean;
|
||||||
reCAPTCHA: boolean;
|
reCAPTCHA: boolean;
|
||||||
reCAPTCHASiteKey: string;
|
reCAPTCHASiteKey: string;
|
||||||
|
userPageEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLangSelector("form");
|
loadLangSelector("form");
|
||||||
@ -343,6 +344,10 @@ const create = (event: SubmitEvent) => {
|
|||||||
const url = ((document.getElementById("modal-success") as HTMLDivElement).querySelector("a.submit") as HTMLAnchorElement).href;
|
const url = ((document.getElementById("modal-success") as HTMLDivElement).querySelector("a.submit") as HTMLAnchorElement).href;
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
} else {
|
} else {
|
||||||
|
if (window.userPageEnabled) {
|
||||||
|
const userPageNoticeArea = document.getElementById("modal-success-user-page-area");
|
||||||
|
userPageNoticeArea.textContent = userPageNoticeArea.textContent.replace("{myAccount}", userPageNoticeArea.getAttribute("my-account-term"));
|
||||||
|
}
|
||||||
window.successModal.show();
|
window.successModal.show();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,6 +110,7 @@ declare interface Modals {
|
|||||||
discord: Modal;
|
discord: Modal;
|
||||||
matrix: Modal;
|
matrix: Modal;
|
||||||
sendPWR?: Modal;
|
sendPWR?: Modal;
|
||||||
|
pwr?: Modal;
|
||||||
logs: Modal;
|
logs: Modal;
|
||||||
email?: Modal;
|
email?: Modal;
|
||||||
}
|
}
|
||||||
|
39
ts/user.ts
39
ts/user.ts
@ -16,6 +16,7 @@ interface userWindow extends Window {
|
|||||||
discordInviteLink: boolean;
|
discordInviteLink: boolean;
|
||||||
matrixUserID: string;
|
matrixUserID: string;
|
||||||
discordSendPINMessage: string;
|
discordSendPINMessage: string;
|
||||||
|
pwrEnabled: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare var window: userWindow;
|
declare var window: userWindow;
|
||||||
@ -44,10 +45,45 @@ window.modals = {} as Modals;
|
|||||||
if (window.matrixEnabled) {
|
if (window.matrixEnabled) {
|
||||||
window.modals.matrix = new Modal(document.getElementById("modal-matrix"), false);
|
window.modals.matrix = new Modal(document.getElementById("modal-matrix"), false);
|
||||||
}
|
}
|
||||||
|
if (window.pwrEnabled) {
|
||||||
|
window.modals.pwr = new Modal(document.getElementById("modal-pwr"), false);
|
||||||
|
window.modals.pwr.onclose = () => {
|
||||||
|
window.modals.login.show();
|
||||||
|
};
|
||||||
|
const resetButton = document.getElementById("modal-login-pwr");
|
||||||
|
resetButton.onclick = () => {
|
||||||
|
const usernameInput = document.getElementById("login-user") as HTMLInputElement;
|
||||||
|
const input = document.getElementById("pwr-address") as HTMLInputElement;
|
||||||
|
input.value = usernameInput.value;
|
||||||
|
window.modals.login.close();
|
||||||
|
window.modals.pwr.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
||||||
|
|
||||||
|
if (window.pwrEnabled && window.linkResetEnabled) {
|
||||||
|
const submitButton = document.getElementById("pwr-submit");
|
||||||
|
const input = document.getElementById("pwr-address") as HTMLInputElement;
|
||||||
|
submitButton.onclick = () => {
|
||||||
|
toggleLoader(submitButton);
|
||||||
|
_post("/my/password/reset/" + input.value, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
toggleLoader(submitButton);
|
||||||
|
if (req.status != 204) {
|
||||||
|
window.notifications.customError("unkownError", window.lang.notif("errorUnknown"));;
|
||||||
|
window.modals.pwr.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.modals.pwr.modal.querySelector(".heading").textContent = window.lang.strings("resetSent");
|
||||||
|
window.modals.pwr.modal.querySelector(".content").textContent = window.lang.strings("resetSentDescription");
|
||||||
|
submitButton.classList.add("unfocused");
|
||||||
|
input.classList.add("unfocused");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const grid = document.querySelector(".grid");
|
const grid = document.querySelector(".grid");
|
||||||
var rootCard = document.getElementById("card-user");
|
var rootCard = document.getElementById("card-user");
|
||||||
var contactCard = document.getElementById("card-contact");
|
var contactCard = document.getElementById("card-contact");
|
||||||
@ -391,9 +427,12 @@ document.addEventListener("details-reload", () => {
|
|||||||
|
|
||||||
expiryCard.expiry = details.expiry;
|
expiryCard.expiry = details.expiry;
|
||||||
|
|
||||||
|
const adminBackButton = document.getElementById("admin-back-button") as HTMLAnchorElement;
|
||||||
|
adminBackButton.href = window.location.href.replace("my/account", "");
|
||||||
|
|
||||||
let messageCard = document.getElementById("card-message");
|
let messageCard = document.getElementById("card-message");
|
||||||
if (details.accounts_admin) {
|
if (details.accounts_admin) {
|
||||||
|
adminBackButton.classList.remove("unfocused");
|
||||||
if (typeof(messageCard) == "undefined" || messageCard == null) {
|
if (typeof(messageCard) == "undefined" || messageCard == null) {
|
||||||
messageCard = document.createElement("div");
|
messageCard = document.createElement("div");
|
||||||
messageCard.classList.add("card", "@low", "dark:~d_neutral", "content");
|
messageCard.classList.add("card", "@low", "dark:~d_neutral", "content");
|
||||||
|
4
views.go
4
views.go
@ -157,6 +157,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
"jellyfinLogin": app.jellyfinLogin,
|
"jellyfinLogin": app.jellyfinLogin,
|
||||||
"jfAdminOnly": jfAdminOnly,
|
"jfAdminOnly": jfAdminOnly,
|
||||||
"jfAllowAll": jfAllowAll,
|
"jfAllowAll": jfAllowAll,
|
||||||
|
"userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,6 +178,7 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
|
|||||||
"discordEnabled": discordEnabled,
|
"discordEnabled": discordEnabled,
|
||||||
"matrixEnabled": matrixEnabled,
|
"matrixEnabled": matrixEnabled,
|
||||||
"ombiEnabled": ombiEnabled,
|
"ombiEnabled": ombiEnabled,
|
||||||
|
"pwrEnabled": app.config.Section("password_resets").Key("enabled").MustBool(false),
|
||||||
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
||||||
"notifications": notificationsEnabled,
|
"notifications": notificationsEnabled,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
@ -184,6 +186,7 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
|
|||||||
"validationStrings": app.storage.lang.User[lang].ValidationStrings,
|
"validationStrings": app.storage.lang.User[lang].ValidationStrings,
|
||||||
"language": app.storage.lang.User[lang].JSON,
|
"language": app.storage.lang.User[lang].JSON,
|
||||||
"langName": lang,
|
"langName": lang,
|
||||||
|
"jfLink": app.config.Section("ui").Key("redirect_url").String(),
|
||||||
}
|
}
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
data["telegramUsername"] = app.telegram.username
|
data["telegramUsername"] = app.telegram.username
|
||||||
@ -625,6 +628,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"captcha": app.config.Section("captcha").Key("enabled").MustBool(false),
|
"captcha": app.config.Section("captcha").Key("enabled").MustBool(false),
|
||||||
"reCAPTCHA": app.config.Section("captcha").Key("recaptcha").MustBool(false),
|
"reCAPTCHA": app.config.Section("captcha").Key("recaptcha").MustBool(false),
|
||||||
"reCAPTCHASiteKey": app.config.Section("captcha").Key("recaptcha_site_key").MustString(""),
|
"reCAPTCHASiteKey": app.config.Section("captcha").Key("recaptcha_site_key").MustString(""),
|
||||||
|
"userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false),
|
||||||
}
|
}
|
||||||
if telegram {
|
if telegram {
|
||||||
data["telegramPIN"] = app.telegram.NewAuthToken()
|
data["telegramPIN"] = app.telegram.NewAuthToken()
|
||||||
|
Loading…
Reference in New Issue
Block a user