fix: bug fixes to password reset (INCOMPLETE)

This commit is contained in:
asterisk727 2025-07-28 22:24:53 -07:00 committed by Azalea
parent 39ed8af840
commit c01c40fe45
8 changed files with 92 additions and 31 deletions

View File

@ -53,6 +53,8 @@ export const EN_REF_Welcome = {
'welcome.verify-state-0': 'You haven\'t verified your email. A verification email had been sent to your inbox less than a minute ago. Please check your inbox!',
'welcome.verify-state-1': 'You haven\'t verified your email. We\'ve already sent 3 emails over the last 24 hours so we\'ll not send another one. Please check your inbox!',
'welcome.verify-state-2': 'You haven\'t verified your email. We just sent you another verification email. Please check your inbox!',
'welcome.reset-state-0': 'A reset email had been sent to your inbox less than a minute ago. Please check your inbox!',
'welcome.reset-state-1': 'We\'ve already sent 3 emails over the last 24 hours so we\'ll not send another one. Please check your inbox!',
'welcome.verifying': 'Verifying your email... please wait.',
'welcome.verified': 'Your email has been verified! You can now log in now.',
'welcome.verification-failed': 'Verification failed: ${message}. Please try again.',

View File

@ -167,7 +167,7 @@ async function resetPassword(user: { email: string, turnstile: string }) {
return await post('api/v2/user/reset-password', user)
}
async function changePassword(user: { code: string, password: string }) {
async function changePassword(user: { token: string, password: string }) {
return await post('/api/v2/user/change-password', user)
}

View File

@ -20,20 +20,20 @@
let error = ""
let verifyMsg = ""
let code = ""
let token = ""
if (USER.isLoggedIn()) {
window.location.href = "/home"
}
if (params.get('code')) {
code = params.get('code')!
token = params.get('code')!
if (location.pathname === '/verify') {
state = 'verify'
verifyMsg = t("welcome.verifying")
submitting = true
// Send request to server
USER.confirmEmail(code)
USER.confirmEmail(token)
.then(() => {
verifyMsg = t('welcome.verified')
submitting = false
@ -104,7 +104,7 @@
}
else {
error = e.message
submitting = false
submitting = false // unnecessary? see line 113, same for both reset functions
turnstileReset()
}
})
@ -121,6 +121,12 @@
return submitting = false
}
if (TURNSTILE_SITE_KEY && turnstile === "") {
// Sleep for 100ms to allow Turnstile to finish
error = t("welcome.waiting-turnstile")
return setTimeout(resetPassword, 100)
}
// Send request to server
await USER.resetPassword({ email, turnstile })
.then(() => {
@ -129,12 +135,22 @@
verifyMsg = t("welcome.reset-password-sent", { email })
})
.catch(e => {
error = e.message
submitting = false
turnstileReset()
if (e.message === "Reset request rejected - STATE_0") {
state = 'verify'
verifyMsg = t("welcome.reset-state-0")
}
else if (e.message === "Reset request rejected - STATE_1") {
state = 'verify'
verifyMsg = t("welcome.reset-state-1")
}
else {
error = e.message
submitting = false
turnstileReset()
}
})
submitting = false;
submitting = false
}
async function changePassword(): Promise<any> {
@ -145,9 +161,10 @@
return submitting = false
}
// Send request to server
await USER.changePassword({ code, password })
// Send request to server
await USER.changePassword({ token, password })
.then(() => {
state = 'verify'
verifyMsg = t("welcome.password-reset-done")
})
.catch(e => {
@ -174,11 +191,13 @@
{#if error}
<span class="error">{error}</span>
{/if}
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{#if error != t("welcome.waiting-turnstile")}
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{/if}
{#if isSignup}
<input type="text" placeholder={t('username')} bind:value={username}>
{/if}
@ -191,7 +210,7 @@
{isSignup ? t('welcome.btn-signup') : t('welcome.btn-login')}
{/if}
</button>
{#if !submitting}
{#if state === "login" && !submitting}
<button on:click={() => state = 'submitreset'}>{t('welcome.btn-reset-password')}</button>
{/if}
{#if TURNSTILE_SITE_KEY}
@ -207,11 +226,13 @@
{#if error}
<span class="error">{error}</span>
{/if}
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{#if error != t("welcome.waiting-turnstile")}
<div on:click={() => state = 'login'} on:keypress={() => state = 'login'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{/if}
<input type="email" placeholder={t('email')} bind:value={email}>
<button on:click={resetPassword}>
{#if submitting}
@ -236,7 +257,10 @@
{/if}
</div>
{:else if state === "reset"}
<div class="login-form" transition:slide>
{#if error}
<span class="error">{error}</span>
{/if}
<div class="login-form" transition:slide>
<input type="password" placeholder={t('new-password')} bind:value={password}>
<button on:click={changePassword}>
{#if submitting}

View File

@ -59,7 +59,7 @@ Located at: [icu.samnyan.aqua.net.UserRegistrar](icu/samnyan/aqua/net/UserRegist
* token: String
* **Returns**: User information
**/user/login** : Login with email/username and password. This will also check if the email is verified and send another confirmation
**/user/login** : Login with email/username and password. This will also check if the email is verified and send another confirmation.
* email: String
* password: String
@ -74,6 +74,18 @@ Located at: [icu.samnyan.aqua.net.UserRegistrar](icu/samnyan/aqua/net/UserRegist
* turnstile: String
* **Returns**: Success message
**/user/reset-password** : Send the user a reset password email. This will also check if the email is verified or if many requests were sent recently.
* email: String
* turnstile: String
* **Returns** Success message
**/user/change-password** : Reset a user's password with a token sent through email to the user.
* token: String
* password: String
* **Returns** Success message
**/user/setting** : Validate and set a user setting field.
* token: String

View File

@ -147,7 +147,7 @@ class UserRegistrar(
}
@API("/reset-password")
@Doc("Reset password with a token sent through email to the user, if it exists.", "Success message") // wtf is the second param in this annotation?
@Doc("Reset password with a token sent through email to the user, if it exists.", "Success message")
suspend fun resetPassword(
@RP email: Str, @RP turnstile: Str,
request: HttpServletRequest
@ -163,16 +163,28 @@ class UserRegistrar(
?: return SUCCESS // obviously dont tell them if the email exists or not
// Check if email is verified
if (!user.emailConfirmed && emailProps.enable) 400 - "Email not verified" // maybe similar logic to login here
if (!user.emailConfirmed && emailProps.enable) 400 - "Email not verified"
val resets = async { resetPasswordRepo.findByAquaNetUserAuId(user.auId) }
val lasReset = resets.maxByOrNull { it.createdAt }
if (lastReset?.createdAt?.plusSeconds(60)?.isAfter(Instant.now()) == true) {
400 - "Reset request rejected - STATE_0"
}
// Check if we have sent more than 3 confirmation emails in the last 24 hours
if (confirmations.count { it.createdAt.plusSeconds(60 * 60 * 24).isAfter(Instant.now()) } > 3) {
400 - "Reset request rejected- STATE_1"
}
// Send a password reset email
emailService.sendPasswordReset(user)
return SUCCESS
}
@API("/change-password")
@Doc("Change a user's password given a reset code", "Success message") // again have no idea what it is
@Doc("Change a user's password given a reset code", "Success message")
suspend fun changePassword(
@RP token: Str, @RP password: Str,
request: HttpServletRequest
@ -188,7 +200,7 @@ class UserRegistrar(
if (reset.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired"
// Change the password
async { userRepo.save(reset.aquaNetUser.apply { pwHash = validator.checkPwHash(password) }) } // how...
async { userRepo.save(reset.aquaNetUser.apply { pwHash = validator.checkPwHash(password) }) }
return SUCCESS
}

View File

@ -84,6 +84,9 @@ class EmailService(
.buildEmail()).thenRun { log.info("Verification email sent to ${user.email}") }
}
/**
* Send a reset password email to the user
*/
fun sendPasswordReset (user: AquaNetUser) {
if (!props.enable) return

View File

@ -0,0 +1,8 @@
CREATE TABLE aqua_net_email_password_reset
(
id BIGINT AUTO_INCREMENT NOT NULL,
token VARCHAR(255) NOT NULL,
created_at datetime NOT NULL,
au_id BIGINT NULL,
CONSTRAINT pk_email_password_reset PRIMARY KEY (id)
);

View File

@ -202,7 +202,7 @@
<table border="0" cellpadding="0" cellspacing="0" class="heading_block block-1" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
<tr>
<td class="pad" style="padding-bottom:12px;text-align:center;width:100%;">
<h1 style="margin: 0; color: #292929; direction: ltr; font-family: 'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif; font-size: 32px; font-weight: 700; letter-spacing: normal; line-height: 120%; text-align: left; margin-top: 0; margin-bottom: 0; mso-line-height-alt: 38.4px;"><span class="tinyMce-placeholder">Verify your email!</span></h1>
<h1 style="margin: 0; color: #292929; direction: ltr; font-family: 'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif; font-size: 32px; font-weight: 700; letter-spacing: normal; line-height: 120%; text-align: left; margin-top: 0; margin-bottom: 0; mso-line-height-alt: 38.4px;"><span class="tinyMce-placeholder">Reset your password!</span></h1>
</td>
</tr>
</table>
@ -225,7 +225,7 @@
<w:anchorlock/>
<v:textbox inset="0px,0px,0px,0px">
<center style="color:#ffffff; font-family:'Trebuchet MS', Tahoma, sans-serif; font-size:16px">
<![endif]--><a href="{{url}}" style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#646cff;border-radius:8px;width:auto;border-top:0px solid transparent;font-weight:400;border-right:0px solid transparent;border-bottom:0px solid transparent;border-left:0px solid transparent;padding-top:8px;padding-bottom:8px;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;text-align:center;mso-border-alt:none;word-break:keep-all;" target="_blank"><span style="padding-left:16px;padding-right:16px;font-size:16px;display:inline-block;letter-spacing:normal;"><span style="word-break: break-word; line-height: 32px;">Verify email</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]--></div>
<![endif]--><a href="{{url}}" style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#646cff;border-radius:8px;width:auto;border-top:0px solid transparent;font-weight:400;border-right:0px solid transparent;border-bottom:0px solid transparent;border-left:0px solid transparent;padding-top:8px;padding-bottom:8px;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;text-align:center;mso-border-alt:none;word-break:keep-all;" target="_blank"><span style="padding-left:16px;padding-right:16px;font-size:16px;display:inline-block;letter-spacing:normal;"><span style="word-break: break-word; line-height: 32px;">Reset password</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]--></div>
</td>
</tr>
</table>