mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-11 19:27:26 +08:00
[+] Separate user validator
This commit is contained in:
@@ -7,7 +7,10 @@ import icu.samnyan.aqua.net.components.JWT
|
|||||||
import icu.samnyan.aqua.net.components.TurnstileService
|
import icu.samnyan.aqua.net.components.TurnstileService
|
||||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||||
|
import icu.samnyan.aqua.net.db.AquaUserValidator
|
||||||
|
import icu.samnyan.aqua.net.db.AquaUserValidator.Companion.SETTING_FIELDS
|
||||||
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
|
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
|
||||||
|
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||||
import icu.samnyan.aqua.sega.general.model.Card
|
import icu.samnyan.aqua.sega.general.model.Card
|
||||||
import icu.samnyan.aqua.sega.general.service.CardService
|
import icu.samnyan.aqua.sega.general.service.CardService
|
||||||
@@ -19,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
|
import kotlin.reflect.KMutableProperty
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@API("/api/v2/user")
|
@API("/api/v2/user")
|
||||||
@@ -31,7 +35,8 @@ class UserRegistrar(
|
|||||||
val jwt: JWT,
|
val jwt: JWT,
|
||||||
val confirmationRepo: EmailConfirmationRepo,
|
val confirmationRepo: EmailConfirmationRepo,
|
||||||
val cardRepo: CardRepository,
|
val cardRepo: CardRepository,
|
||||||
val cardService: CardService
|
val cardService: CardService,
|
||||||
|
val validator: AquaUserValidator,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
// Random long with length 19 (10^19 possibilities)
|
// Random long with length 19 (10^19 possibilities)
|
||||||
@@ -53,46 +58,26 @@ class UserRegistrar(
|
|||||||
// Check captcha
|
// Check captcha
|
||||||
if (!turnstileService.validate(turnstile, ip)) 400 - "Invalid captcha"
|
if (!turnstileService.validate(turnstile, ip)) 400 - "Invalid captcha"
|
||||||
|
|
||||||
// Check if email is valid
|
|
||||||
if (!email.isValidEmail()) 400 - "Invalid email"
|
|
||||||
|
|
||||||
// Check if user with the same email exists
|
|
||||||
if (async { userRepo.findByEmailIgnoreCase(email) != null })
|
|
||||||
400 - "User with email `$email` already exists"
|
|
||||||
|
|
||||||
// Check if username is valid
|
|
||||||
if (username.length < 2) 400 - "Username must be at least 2 letters"
|
|
||||||
if (username.length > 32) 400 - "Username too long (max 32 letters)"
|
|
||||||
if (username.contains(" ")) 400 - "Username cannot contain spaces"
|
|
||||||
|
|
||||||
// Check if username is within A-Za-z0-9_-~.
|
|
||||||
username.find { !it.isLetterOrDigit() && it != '_' && it != '-' && it != '~' && it != '.' }?.let {
|
|
||||||
400 - "Username cannot contain `$it`. Please only use letters (A-Z), numbers (0-9), and `_-~.` characters. You can set a display name later."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user with the same username exists
|
|
||||||
if (async { userRepo.findByUsernameIgnoreCase(username) != null })
|
|
||||||
400 - "User with username `$username` already exists"
|
|
||||||
|
|
||||||
// Validate password
|
|
||||||
if (password.length < 8) 400 - "Password must be at least 8 characters"
|
|
||||||
|
|
||||||
// GeoIP check to infer country
|
// GeoIP check to infer country
|
||||||
val country = geoIP.getCountry(ip)
|
val country = geoIP.getCountry(ip)
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
val u = async { AquaNetUser(
|
||||||
|
username = validator.checkUsername(username),
|
||||||
|
email = validator.checkEmail(email),
|
||||||
|
pwHash = validator.checkPwHash(password),
|
||||||
|
regTime = millis(), lastLogin = millis(), country = country,
|
||||||
|
) }
|
||||||
|
|
||||||
// Create a ghost card
|
// Create a ghost card
|
||||||
val card = Card().apply {
|
val card = Card().apply {
|
||||||
extId = cardService.randExtID(cardExtIdStart, cardExtIdEnd)
|
extId = cardService.randExtID(cardExtIdStart, cardExtIdEnd)
|
||||||
luid = extId.toString()
|
luid = extId.toString()
|
||||||
registerTime = LocalDateTime.now()
|
registerTime = LocalDateTime.now()
|
||||||
accessTime = registerTime
|
accessTime = registerTime
|
||||||
|
aquaUser = u
|
||||||
}
|
}
|
||||||
val u = AquaNetUser(
|
u.ghostCard = card
|
||||||
username = username, email = email, pwHash = hasher.encode(password),
|
|
||||||
regTime = millis(), lastLogin = millis(), country = country,
|
|
||||||
ghostCard = card
|
|
||||||
)
|
|
||||||
card.aquaUser = u
|
|
||||||
|
|
||||||
// Save the user
|
// Save the user
|
||||||
async {
|
async {
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
package icu.samnyan.aqua.net.db
|
package icu.samnyan.aqua.net.db
|
||||||
|
|
||||||
|
import ext.Str
|
||||||
|
import ext.isValidEmail
|
||||||
|
import ext.minus
|
||||||
import icu.samnyan.aqua.sega.general.model.Card
|
import icu.samnyan.aqua.sega.general.model.Card
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
|
import kotlin.reflect.KMutableProperty
|
||||||
|
import kotlin.reflect.full.functions
|
||||||
|
|
||||||
@Entity(name = "AquaNetUser")
|
@Entity(name = "AquaNetUser")
|
||||||
@Table(name = "aqua_net_user")
|
@Table(name = "aqua_net_user")
|
||||||
@@ -57,4 +65,77 @@ interface AquaNetUserRepo : JpaRepository<AquaNetUser, Long> {
|
|||||||
fun findByAuId(auId: Long): AquaNetUser?
|
fun findByAuId(auId: Long): AquaNetUser?
|
||||||
fun findByEmailIgnoreCase(email: String): AquaNetUser?
|
fun findByEmailIgnoreCase(email: String): AquaNetUser?
|
||||||
fun findByUsernameIgnoreCase(username: String): AquaNetUser?
|
fun findByUsernameIgnoreCase(username: String): AquaNetUser?
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SettingField(
|
||||||
|
val name: Str,
|
||||||
|
val checker: KFunction<*>,
|
||||||
|
val setter: KMutableProperty.Setter<*>,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is a validator for user fields. It will return the parsed value if the field is valid, or
|
||||||
|
* throw an ApiException if the field is invalid.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
class AquaUserValidator(
|
||||||
|
val userRepo: AquaNetUserRepo,
|
||||||
|
val hasher: PasswordEncoder,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val SETTING_FIELDS = AquaUserValidator::class.functions
|
||||||
|
.filter { it.name.startsWith("check") }
|
||||||
|
.map {
|
||||||
|
val name = it.name.removePrefix("check").replaceFirstChar { c -> c.lowercase() }
|
||||||
|
val prop = AquaNetUser::class.members.find { m -> m.name == name } as KMutableProperty<*>
|
||||||
|
SettingField(name, it, prop.setter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkUsername(username: Str) = username.apply {
|
||||||
|
// Check if username is valid
|
||||||
|
if (length < 2) 400 - "Username must be at least 2 letters"
|
||||||
|
if (length > 32) 400 - "Username too long (max 32 letters)"
|
||||||
|
if (contains(" ")) 400 - "Username cannot contain spaces"
|
||||||
|
|
||||||
|
// Check if username is within A-Za-z0-9_-~.
|
||||||
|
find { !it.isLetterOrDigit() && it != '_' && it != '-' && it != '~' && it != '.' }?.let {
|
||||||
|
400 - "Username cannot contain `$it`. Please only use letters (A-Z), numbers (0-9), and `_-~.` characters. You can set a display name later."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user with the same username exists
|
||||||
|
if (userRepo.findByUsernameIgnoreCase(this) != null)
|
||||||
|
400 - "User with username `$this` already exists"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkEmail(email: Str) = email.apply {
|
||||||
|
// Check if email is valid
|
||||||
|
if (!isValidEmail()) 400 - "Invalid email"
|
||||||
|
|
||||||
|
// Check if user with the same email exists
|
||||||
|
if (userRepo.findByEmailIgnoreCase(email) != null)
|
||||||
|
400 - "User with email `$email` already exists"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkPwHash(password: Str) = password.run {
|
||||||
|
// Validate password
|
||||||
|
if (length < 8) 400 - "Password must be at least 8 characters"
|
||||||
|
|
||||||
|
hasher.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkDisplayName(displayName: Str) = displayName.apply {
|
||||||
|
// Check if display name is valid
|
||||||
|
if (length > 32) 400 - "Display name too long (max 32 letters)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkProfileLocation(profileLocation: Str) = profileLocation.apply {
|
||||||
|
// Check if profile location is valid
|
||||||
|
if (length > 64) 400 - "Profile location too long (max 64 letters)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkProfileBio(profileBio: Str) = profileBio.apply {
|
||||||
|
// Check if profile bio is valid
|
||||||
|
if (length > 255) 400 - "Profile bio too long (max 255 letters)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user