mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-07 02:37:26 +08:00
feature: password reset
This commit is contained in:
@@ -29,6 +29,7 @@ class UserRegistrar(
|
||||
val geoIP: GeoIP,
|
||||
val jwt: JWT,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
val resetPasswordRepo: ResetPasswordRepo,
|
||||
val cardRepo: CardRepository,
|
||||
val cardService: CardService,
|
||||
val validator: AquaUserServices,
|
||||
@@ -144,6 +145,53 @@ class UserRegistrar(
|
||||
return mapOf("token" to token)
|
||||
}
|
||||
|
||||
@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?
|
||||
suspend fun resetPassword(
|
||||
@RP email: Str, @RP turnstile: Str,
|
||||
request: HttpServletRequest
|
||||
) : Any {
|
||||
|
||||
// Check captcha
|
||||
val ip = geoIP.getIP(request)
|
||||
log.info("Net: /user/reset-password from $ip : $email")
|
||||
if (!turnstileService.validate(turnstile, ip)) 400 - "Invalid captcha"
|
||||
|
||||
// Check if user exists, treat as email / username
|
||||
val user = async { userRepo.findByEmailIgnoreCase(email) ?: userRepo.findByUsernameIgnoreCase(email) }
|
||||
?: 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
|
||||
|
||||
// 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
|
||||
suspend fun changePassword(
|
||||
@RP token: Str, @RP password: Str,
|
||||
request: HttpServletRequest
|
||||
) : Any {
|
||||
|
||||
// Find the reset token
|
||||
val reset = async { resetPasswordRepo.findByToken(token) }
|
||||
|
||||
// Check if the token is valid
|
||||
if (reset == null) 400 - "Invalid token"
|
||||
|
||||
// Check if the token is expired
|
||||
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...
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
@API("/confirm-email")
|
||||
@Doc("Confirm email address with a token sent through email to the user.", "Success message")
|
||||
suspend fun confirmEmail(@RP token: Str): Any {
|
||||
|
||||
@@ -5,6 +5,7 @@ import ext.Str
|
||||
import ext.logger
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmation
|
||||
import icu.samnyan.aqua.net.db.PasswordReset
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
|
||||
import org.simplejavamail.api.mailer.Mailer
|
||||
import org.simplejavamail.email.EmailBuilder
|
||||
@@ -38,10 +39,13 @@ class EmailService(
|
||||
val mailer: Mailer,
|
||||
val props: EmailProperties,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
val resetPasswordRepo: ResetPasswordRepo,
|
||||
) {
|
||||
val log = logger()
|
||||
val confirmTemplate: Str = this::class.java.getResource("/email/confirm.html")?.readText()
|
||||
?: throw Exception("Email Template Not Found")
|
||||
?: throw Exception("Email Confirm Template Not Found")
|
||||
val resetTemplate: Str = this::class.java.getResource("/email/reset.html")?.readText()
|
||||
?: throw Exception("Password Reset Template Not Found")
|
||||
|
||||
@Async
|
||||
@EventListener(ApplicationStartedEvent::class)
|
||||
@@ -80,6 +84,26 @@ class EmailService(
|
||||
.buildEmail()).thenRun { log.info("Verification email sent to ${user.email}") }
|
||||
}
|
||||
|
||||
fun sendPasswordReset (user: AquaNetUser) {
|
||||
if (!props.enable) return
|
||||
|
||||
// Generate token (UUID4)
|
||||
val token = UUID.randomUUID().toString()
|
||||
val reset = ResetPassword(token = token, aquaNetUser = user, createdAt = Date().toInstant())
|
||||
resetPasswordRepo.save(reset)
|
||||
|
||||
// Send email
|
||||
log.info("Sending reset password email to ${user.email}")
|
||||
mailer.sendMail(EmailBuilder.startingBlank()
|
||||
.from(props.senderName, props.senderAddr)
|
||||
.to(user.computedName, user.email)
|
||||
.withSubject("Reset Your Password for AquaNet")
|
||||
.withHTMLText(resetTemplate
|
||||
.replace("{{name}}", user.computedName)
|
||||
.replace("{{url}}", "https://${props.webHost}/reset-password?code=$token"))
|
||||
.buildEmail()).thenRun { log.info("Reset password email sent to ${user.email}") }
|
||||
}
|
||||
|
||||
fun testEmail(addr: Str, name: Str) {
|
||||
if (!props.enable) return
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package icu.samnyan.aqua.net.db
|
||||
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
@Entity
|
||||
@Table(name = "aqua_net_email_reset_password")
|
||||
class ResetPassword(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@Column(nullable = false)
|
||||
var token: String = "",
|
||||
|
||||
// Token creation time
|
||||
@Column(nullable = false)
|
||||
var createdAt: Instant = Instant.now(),
|
||||
|
||||
// Linking to the AquaNetUser
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "auId", referencedColumnName = "auId")
|
||||
var aquaNetUser: AquaNetUser = AquaNetUser()
|
||||
) : Serializable
|
||||
|
||||
@Repository
|
||||
interface ResetPasswordRepo : JpaRepository<ResetPassword, Long> {
|
||||
fun findByToken(token: String): ResetPassword?
|
||||
fun findByAquaNetUserAuId(auId: Long): List<ResetPassword>
|
||||
}
|
||||
Reference in New Issue
Block a user