mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
userpage: implement change password functionality
This commit is contained in:
parent
12ce669566
commit
97db4d714a
@ -275,6 +275,7 @@ func (app *appContext) ModifyMyEmail(gc *gin.Context) {
|
|||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Param invCode path string true "invite Code"
|
// @Param invCode path string true "invite Code"
|
||||||
// @Router /my/discord/invite [get]
|
// @Router /my/discord/invite [get]
|
||||||
|
// @Security Bearer
|
||||||
// @tags User Page
|
// @tags User Page
|
||||||
func (app *appContext) MyDiscordServerInvite(gc *gin.Context) {
|
func (app *appContext) MyDiscordServerInvite(gc *gin.Context) {
|
||||||
if app.discord.inviteChannelName == "" {
|
if app.discord.inviteChannelName == "" {
|
||||||
@ -295,6 +296,7 @@ func (app *appContext) MyDiscordServerInvite(gc *gin.Context) {
|
|||||||
// @Failure 400 {object} stringResponse
|
// @Failure 400 {object} stringResponse
|
||||||
// Param service path string true "discord/telegram"
|
// Param service path string true "discord/telegram"
|
||||||
// @Router /my/pin/{service} [get]
|
// @Router /my/pin/{service} [get]
|
||||||
|
// @Security Bearer
|
||||||
// @tags User Page
|
// @tags User Page
|
||||||
func (app *appContext) GetMyPIN(gc *gin.Context) {
|
func (app *appContext) GetMyPIN(gc *gin.Context) {
|
||||||
service := gc.Param("service")
|
service := gc.Param("service")
|
||||||
@ -319,6 +321,7 @@ func (app *appContext) GetMyPIN(gc *gin.Context) {
|
|||||||
// @Failure 401 {object} boolResponse
|
// @Failure 401 {object} boolResponse
|
||||||
// @Param pin path string true "PIN code to check"
|
// @Param pin path string true "PIN code to check"
|
||||||
// @Router /my/discord/verified/{pin} [get]
|
// @Router /my/discord/verified/{pin} [get]
|
||||||
|
// @Security Bearer
|
||||||
// @tags User Page
|
// @tags User Page
|
||||||
func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
|
func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
|
||||||
pin := gc.Param("pin")
|
pin := gc.Param("pin")
|
||||||
@ -347,6 +350,7 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
|
|||||||
// @Failure 401 {object} boolResponse
|
// @Failure 401 {object} boolResponse
|
||||||
// @Param pin path string true "PIN code to check"
|
// @Param pin path string true "PIN code to check"
|
||||||
// @Router /my/telegram/verified/{pin} [get]
|
// @Router /my/telegram/verified/{pin} [get]
|
||||||
|
// @Security Bearer
|
||||||
// @tags User Page
|
// @tags User Page
|
||||||
func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
|
func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
|
||||||
pin := gc.Param("pin")
|
pin := gc.Param("pin")
|
||||||
@ -386,6 +390,7 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
|
|||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
|
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
|
||||||
// @Router /my/matrix/user [post]
|
// @Router /my/matrix/user [post]
|
||||||
|
// @Security Bearer
|
||||||
// @tags User Page
|
// @tags User Page
|
||||||
func (app *appContext) MatrixSendMyPIN(gc *gin.Context) {
|
func (app *appContext) MatrixSendMyPIN(gc *gin.Context) {
|
||||||
var req MatrixSendPINDTO
|
var req MatrixSendPINDTO
|
||||||
@ -419,6 +424,7 @@ func (app *appContext) MatrixSendMyPIN(gc *gin.Context) {
|
|||||||
// @Param invCode path string true "invite Code"
|
// @Param invCode path string true "invite Code"
|
||||||
// @Param userID path string true "Matrix User ID"
|
// @Param userID path string true "Matrix User ID"
|
||||||
// @Router /my/matrix/verified/{userID}/{pin} [get]
|
// @Router /my/matrix/verified/{userID}/{pin} [get]
|
||||||
|
// @Security Bearer
|
||||||
// @tags User Page
|
// @tags User Page
|
||||||
func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
|
func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
|
||||||
userID := gc.Param("userID")
|
userID := gc.Param("userID")
|
||||||
@ -452,7 +458,8 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Router /my/discord [delete]
|
// @Router /my/discord [delete]
|
||||||
// @Tags Users
|
// @Security Bearer
|
||||||
|
// @Tags User Page
|
||||||
func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
|
func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
|
||||||
app.storage.DeleteDiscordKey(gc.GetString("jfId"))
|
app.storage.DeleteDiscordKey(gc.GetString("jfId"))
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
@ -462,7 +469,8 @@ func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Router /my/telegram [delete]
|
// @Router /my/telegram [delete]
|
||||||
// @Tags Users
|
// @Security Bearer
|
||||||
|
// @Tags User Page
|
||||||
func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
|
func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
|
||||||
app.storage.DeleteTelegramKey(gc.GetString("jfId"))
|
app.storage.DeleteTelegramKey(gc.GetString("jfId"))
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
@ -472,7 +480,8 @@ func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Router /my/matrix [delete]
|
// @Router /my/matrix [delete]
|
||||||
// @Tags Users
|
// @Security Bearer
|
||||||
|
// @Tags User Page
|
||||||
func (app *appContext) UnlinkMyMatrix(gc *gin.Context) {
|
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)
|
||||||
@ -485,7 +494,7 @@ func (app *appContext) UnlinkMyMatrix(gc *gin.Context) {
|
|||||||
// @Failure 400 {object} boolResponse
|
// @Failure 400 {object} boolResponse
|
||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Router /my/password/reset/{address} [post]
|
// @Router /my/password/reset/{address} [post]
|
||||||
// @tags Users
|
// @Tags User Page
|
||||||
func (app *appContext) ResetMyPassword(gc *gin.Context) {
|
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.
|
// All requests should take 1 second, to make it harder to tell if a success occured or not.
|
||||||
timerWait := make(chan bool)
|
timerWait := make(chan bool)
|
||||||
@ -551,3 +560,63 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Change your password, given the old one and the new one.
|
||||||
|
// @Produce json
|
||||||
|
// @Param ChangeMyPasswordDTO body ChangeMyPasswordDTO true "User's old & new passwords."
|
||||||
|
// @Success 204 {object} boolResponse
|
||||||
|
// @Failure 400 {object} PasswordValidation
|
||||||
|
// @Failure 401 {object} boolResponse
|
||||||
|
// @Failure 500 {object} boolResponse
|
||||||
|
// @Router /my/password [post]
|
||||||
|
// @Security Bearer
|
||||||
|
// @Tags User Page
|
||||||
|
func (app *appContext) ChangeMyPassword(gc *gin.Context) {
|
||||||
|
var req ChangeMyPasswordDTO
|
||||||
|
gc.BindJSON(&req)
|
||||||
|
if req.Old == "" || req.New == "" {
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
}
|
||||||
|
validation := app.validator.validate(req.New)
|
||||||
|
for _, val := range validation {
|
||||||
|
if !val {
|
||||||
|
app.debug.Printf("%s: Change password failed: Invalid password", gc.GetString("jfId"))
|
||||||
|
gc.JSON(400, validation)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user, status, err := app.jf.UserByID(gc.GetString("jfId"), false)
|
||||||
|
if status != 200 || err != nil {
|
||||||
|
app.err.Printf("Failed to change password: couldn't find user (%d): %+v", status, err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Authenticate as user to confirm old password.
|
||||||
|
user, status, err = app.authJf.Authenticate(user.Name, req.Old)
|
||||||
|
if status != 200 || err != nil {
|
||||||
|
respondBool(401, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status, err = app.jf.SetPassword(gc.GetString("jfId"), req.Old, req.New)
|
||||||
|
if (status != 200 && status != 204) || err != nil {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
|
ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId"))
|
||||||
|
if status != 200 || err != nil {
|
||||||
|
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", user.Name, status, err)
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ombiUser["password"] = req.New
|
||||||
|
status, err = app.ombi.ModifyUser(ombiUser)
|
||||||
|
if status != 200 || err != nil {
|
||||||
|
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
|
||||||
|
}
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
}
|
||||||
|
@ -463,6 +463,7 @@ func (app *appContext) NewUser(gc *gin.Context) {
|
|||||||
for _, val := range validation {
|
for _, val := range validation {
|
||||||
if !val {
|
if !val {
|
||||||
valid = false
|
valid = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !valid {
|
if !valid {
|
||||||
|
@ -124,7 +124,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-initial">
|
<div class="flex-initial">
|
||||||
<div class="card ~neutral @low mb-4">
|
<div class="card ~neutral @low mb-4">
|
||||||
<span class="label supra" for="inv-uses">{{ .strings.passwordRequirementsHeader }}</span>
|
<span class="label supra">{{ .strings.passwordRequirementsHeader }}</span>
|
||||||
<ul>
|
<ul>
|
||||||
{{ range $key, $value := .requirements }}
|
{{ range $key, $value := .requirements }}
|
||||||
<li class="" id="requirement-{{ $key }}" min="{{ $value }}">
|
<li class="" id="requirement-{{ $key }}" min="{{ $value }}">
|
||||||
|
@ -50,6 +50,8 @@
|
|||||||
"errorCaptcha": "Captcha incorrect.",
|
"errorCaptcha": "Captcha incorrect.",
|
||||||
"errorPassword": "Check password requirements.",
|
"errorPassword": "Check password requirements.",
|
||||||
"errorNoMatch": "Passwords don't match.",
|
"errorNoMatch": "Passwords don't match.",
|
||||||
|
"errorOldPassword": "Old password incorrect.",
|
||||||
|
"passwordChanged": "Password Changed.",
|
||||||
"verified": "Account verified."
|
"verified": "Account verified."
|
||||||
},
|
},
|
||||||
"validationStrings": {
|
"validationStrings": {
|
||||||
|
@ -408,3 +408,8 @@ const (
|
|||||||
type GetMyPINDTO struct {
|
type GetMyPINDTO struct {
|
||||||
PIN string `json:"pin"`
|
PIN string `json:"pin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChangeMyPasswordDTO struct {
|
||||||
|
Old string `json:"old"`
|
||||||
|
New string `json:"new"`
|
||||||
|
}
|
||||||
|
@ -241,6 +241,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
user.DELETE(p+"/discord", app.UnlinkMyDiscord)
|
user.DELETE(p+"/discord", app.UnlinkMyDiscord)
|
||||||
user.DELETE(p+"/telegram", app.UnlinkMyTelegram)
|
user.DELETE(p+"/telegram", app.UnlinkMyTelegram)
|
||||||
user.DELETE(p+"/matrix", app.UnlinkMyMatrix)
|
user.DELETE(p+"/matrix", app.UnlinkMyMatrix)
|
||||||
|
user.POST(p+"/password", app.ChangeMyPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
ts/form.ts
13
ts/form.ts
@ -2,7 +2,7 @@ import { Modal } from "./modules/modal.js";
|
|||||||
import { notificationBox, whichAnimationEvent } from "./modules/common.js";
|
import { notificationBox, whichAnimationEvent } from "./modules/common.js";
|
||||||
import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
|
import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
|
||||||
import { loadLangSelector } from "./modules/lang.js";
|
import { loadLangSelector } from "./modules/lang.js";
|
||||||
import { Validator, ValidatorConf } from "./modules/validator.js";
|
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
||||||
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends Window {
|
||||||
@ -257,11 +257,6 @@ if (window.emailRequired) {
|
|||||||
emailField.addEventListener("keyup", validator.validate)
|
emailField.addEventListener("keyup", validator.validate)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface respDTO {
|
|
||||||
response: boolean;
|
|
||||||
error: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface sendDTO {
|
interface sendDTO {
|
||||||
code: string;
|
code: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -340,11 +335,11 @@ const create = (event: SubmitEvent) => {
|
|||||||
}
|
}
|
||||||
_post("/newUser", send, (req: XMLHttpRequest) => {
|
_post("/newUser", send, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
let vals = req.response as respDTO;
|
let vals = req.response as ValidatorRespDTO;
|
||||||
let valid = true;
|
let valid = true;
|
||||||
for (let type in vals) {
|
for (let type in vals) {
|
||||||
if (requirements[type]) { requirements[type].valid = vals[type]; }
|
if (requirements[type]) requirements[type].valid = vals[type];
|
||||||
if (!vals[type]) { valid = false; }
|
if (!vals[type]) valid = false;
|
||||||
}
|
}
|
||||||
if (req.status == 200 && valid) {
|
if (req.status == 200 && valid) {
|
||||||
if (window.redirectToJellyfin == true) {
|
if (window.redirectToJellyfin == true) {
|
||||||
|
@ -9,6 +9,11 @@ interface pwValString {
|
|||||||
plural: string;
|
plural: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ValidatorRespDTO {
|
||||||
|
response: boolean;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface pwValStrings {
|
interface pwValStrings {
|
||||||
length: pwValString;
|
length: pwValString;
|
||||||
uppercase: pwValString;
|
uppercase: pwValString;
|
||||||
|
28
ts/user.ts
28
ts/user.ts
@ -1,10 +1,10 @@
|
|||||||
import { ThemeManager } from "./modules/theme.js";
|
import { ThemeManager } from "./modules/theme.js";
|
||||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { _get, _post, _delete, notificationBox, whichAnimationEvent, toDateString, toggleLoader } from "./modules/common.js";
|
import { _get, _post, _delete, notificationBox, whichAnimationEvent, toDateString, toggleLoader, addLoader, removeLoader } from "./modules/common.js";
|
||||||
import { Login } from "./modules/login.js";
|
import { Login } from "./modules/login.js";
|
||||||
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
||||||
import { Validator, ValidatorConf } from "./modules/validator.js";
|
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
||||||
|
|
||||||
interface userWindow extends Window {
|
interface userWindow extends Window {
|
||||||
jellyfinID: string;
|
jellyfinID: string;
|
||||||
@ -405,9 +405,31 @@ let validatorConf: ValidatorConf = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let validator = new Validator(validatorConf);
|
let validator = new Validator(validatorConf);
|
||||||
let requirements = validator.requirements;
|
// let requirements = validator.requirements;
|
||||||
|
|
||||||
oldPasswordField.addEventListener("keyup", validator.validate);
|
oldPasswordField.addEventListener("keyup", validator.validate);
|
||||||
|
changePasswordButton.addEventListener("click", () => {
|
||||||
|
addLoader(changePasswordButton);
|
||||||
|
_post("/my/password", { old: oldPasswordField.value, new: newPasswordField.value }, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
removeLoader(changePasswordButton);
|
||||||
|
if (req.status == 400) {
|
||||||
|
window.notifications.customError("errorPassword", window.lang.notif("errorPassword"));
|
||||||
|
} else if (req.status == 500) {
|
||||||
|
window.notifications.customError("errorUnknown", window.lang.notif("errorUnknown"));
|
||||||
|
} else if (req.status == 204) {
|
||||||
|
window.notifications.customSuccess("passwordChanged", window.lang.notif("passwordChanged"));
|
||||||
|
setTimeout(() => { window.location.reload() }, 2000);
|
||||||
|
}
|
||||||
|
}, true, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
if (req.status == 401) {
|
||||||
|
removeLoader(changePasswordButton);
|
||||||
|
window.notifications.customError("oldPasswordError", window.lang.notif("errorOldPassword"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
// FIXME: Submit & Validate
|
// FIXME: Submit & Validate
|
||||||
|
|
||||||
document.addEventListener("details-reload", () => {
|
document.addEventListener("details-reload", () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user