Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
bd0801a8
"backend/vscode:/vscode.git/clone" did not exist on "bf6fe5e9626c2f32ef5d12cc86003f5c9585c1fd"
Commit
bd0801a8
authored
Mar 02, 2026
by
PMExtra
Browse files
feat(registration): add email domain whitelist policy
parent
ba6de4c4
Changes
25
Hide whitespace changes
Inline
Side-by-side
frontend/src/utils/authError.ts
0 → 100644
View file @
bd0801a8
interface
APIErrorLike
{
message
?:
string
response
?:
{
data
?:
{
detail
?:
string
message
?:
string
}
}
}
function
extractErrorMessage
(
error
:
unknown
):
string
{
const
err
=
(
error
||
{})
as
APIErrorLike
return
err
.
response
?.
data
?.
detail
||
err
.
response
?.
data
?.
message
||
err
.
message
||
''
}
export
function
buildAuthErrorMessage
(
error
:
unknown
,
options
:
{
fallback
:
string
}
):
string
{
const
{
fallback
}
=
options
const
message
=
extractErrorMessage
(
error
)
return
message
||
fallback
}
frontend/src/utils/registrationEmailPolicy.ts
0 → 100644
View file @
bd0801a8
const
EMAIL_SUFFIX_TOKEN_SPLIT_RE
=
/
[\s
,,
]
+/
const
EMAIL_SUFFIX_INVALID_CHAR_RE
=
/
[^
a-z0-9.-
]
/g
const
EMAIL_SUFFIX_INVALID_CHAR_CHECK_RE
=
/
[^
a-z0-9.-
]
/
const
EMAIL_SUFFIX_PREFIX_RE
=
/^@+/
const
EMAIL_SUFFIX_DOMAIN_PATTERN
=
/^
[
a-z0-9
](?:[
a-z0-9-
]{0,61}[
a-z0-9
])?(?:\.[
a-z0-9
](?:[
a-z0-9-
]{0,61}[
a-z0-9
])?)
+$/
// normalizeRegistrationEmailSuffixDomain converts raw input into a canonical domain token.
// It removes leading "@", lowercases input, and strips all invalid characters.
export
function
normalizeRegistrationEmailSuffixDomain
(
raw
:
string
):
string
{
let
value
=
String
(
raw
||
''
).
trim
().
toLowerCase
()
if
(
!
value
)
{
return
''
}
value
=
value
.
replace
(
EMAIL_SUFFIX_PREFIX_RE
,
''
)
value
=
value
.
replace
(
EMAIL_SUFFIX_INVALID_CHAR_RE
,
''
)
return
value
}
export
function
normalizeRegistrationEmailSuffixDomains
(
items
:
string
[]
|
null
|
undefined
):
string
[]
{
if
(
!
items
||
items
.
length
===
0
)
{
return
[]
}
const
seen
=
new
Set
<
string
>
()
const
normalized
:
string
[]
=
[]
for
(
const
item
of
items
)
{
const
domain
=
normalizeRegistrationEmailSuffixDomain
(
item
)
if
(
!
isRegistrationEmailSuffixDomainValid
(
domain
)
||
seen
.
has
(
domain
))
{
continue
}
seen
.
add
(
domain
)
normalized
.
push
(
domain
)
}
return
normalized
}
export
function
parseRegistrationEmailSuffixWhitelistInput
(
input
:
string
):
string
[]
{
if
(
!
input
||
!
input
.
trim
())
{
return
[]
}
const
seen
=
new
Set
<
string
>
()
const
normalized
:
string
[]
=
[]
for
(
const
token
of
input
.
split
(
EMAIL_SUFFIX_TOKEN_SPLIT_RE
))
{
const
domain
=
normalizeRegistrationEmailSuffixDomainStrict
(
token
)
if
(
!
isRegistrationEmailSuffixDomainValid
(
domain
)
||
seen
.
has
(
domain
))
{
continue
}
seen
.
add
(
domain
)
normalized
.
push
(
domain
)
}
return
normalized
}
export
function
normalizeRegistrationEmailSuffixWhitelist
(
items
:
string
[]
|
null
|
undefined
):
string
[]
{
return
normalizeRegistrationEmailSuffixDomains
(
items
).
map
((
domain
)
=>
`@
${
domain
}
`
)
}
function
extractRegistrationEmailDomain
(
email
:
string
):
string
{
const
raw
=
String
(
email
||
''
).
trim
().
toLowerCase
()
if
(
!
raw
)
{
return
''
}
const
atIndex
=
raw
.
indexOf
(
'
@
'
)
if
(
atIndex
<=
0
||
atIndex
>=
raw
.
length
-
1
)
{
return
''
}
if
(
raw
.
indexOf
(
'
@
'
,
atIndex
+
1
)
!==
-
1
)
{
return
''
}
return
raw
.
slice
(
atIndex
+
1
)
}
export
function
isRegistrationEmailSuffixAllowed
(
email
:
string
,
whitelist
:
string
[]
|
null
|
undefined
):
boolean
{
const
normalizedWhitelist
=
normalizeRegistrationEmailSuffixWhitelist
(
whitelist
)
if
(
normalizedWhitelist
.
length
===
0
)
{
return
true
}
const
emailDomain
=
extractRegistrationEmailDomain
(
email
)
if
(
!
emailDomain
)
{
return
false
}
const
emailSuffix
=
`@
${
emailDomain
}
`
return
normalizedWhitelist
.
includes
(
emailSuffix
)
}
// Pasted domains should be strict: any invalid character drops the whole token.
function
normalizeRegistrationEmailSuffixDomainStrict
(
raw
:
string
):
string
{
let
value
=
String
(
raw
||
''
).
trim
().
toLowerCase
()
if
(
!
value
)
{
return
''
}
value
=
value
.
replace
(
EMAIL_SUFFIX_PREFIX_RE
,
''
)
if
(
!
value
||
EMAIL_SUFFIX_INVALID_CHAR_CHECK_RE
.
test
(
value
))
{
return
''
}
return
value
}
export
function
isRegistrationEmailSuffixDomainValid
(
domain
:
string
):
boolean
{
if
(
!
domain
)
{
return
false
}
return
EMAIL_SUFFIX_DOMAIN_PATTERN
.
test
(
domain
)
}
frontend/src/views/admin/SettingsView.vue
View file @
bd0801a8
...
...
@@ -324,6 +324,56 @@
<Toggle
v-model=
"form.email_verify_enabled"
/>
</div>
<!-- Email Suffix Whitelist -->
<div
class=
"border-t border-gray-100 pt-4 dark:border-dark-700"
>
<label
class=
"font-medium text-gray-900 dark:text-white"
>
{{
t('admin.settings.registration.emailSuffixWhitelist')
}}
</label>
<p
class=
"mt-1 text-sm text-gray-500 dark:text-gray-400"
>
{{ t('admin.settings.registration.emailSuffixWhitelistHint') }}
</p>
<div
class=
"mt-3 rounded-lg border border-gray-300 bg-white p-2 dark:border-dark-500 dark:bg-dark-700"
>
<div
class=
"flex flex-wrap items-center gap-2"
>
<span
v-for=
"suffix in registrationEmailSuffixWhitelistTags"
:key=
"suffix"
class=
"inline-flex items-center gap-1 rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-700 dark:bg-dark-600 dark:text-gray-200"
>
<span
class=
"text-gray-400 dark:text-gray-500"
>
@
</span>
<span>
{{ suffix }}
</span>
<button
type=
"button"
class=
"rounded-full text-gray-500 hover:bg-gray-200 hover:text-gray-700 dark:text-gray-300 dark:hover:bg-dark-500 dark:hover:text-white"
@
click=
"removeRegistrationEmailSuffixWhitelistTag(suffix)"
>
<Icon
name=
"x"
size=
"xs"
class=
"h-3.5 w-3.5"
:stroke-width=
"2"
/>
</button>
</span>
<div
class=
"flex min-w-[220px] flex-1 items-center gap-1 rounded border border-transparent px-2 py-1 focus-within:border-primary-300 dark:focus-within:border-primary-700"
>
<span
class=
"font-mono text-sm text-gray-400 dark:text-gray-500"
>
@
</span>
<input
v-model=
"registrationEmailSuffixWhitelistDraft"
type=
"text"
class=
"w-full bg-transparent text-sm font-mono text-gray-900 outline-none placeholder:text-gray-400 dark:text-white dark:placeholder:text-gray-500"
:placeholder=
"t('admin.settings.registration.emailSuffixWhitelistPlaceholder')"
@
input=
"handleRegistrationEmailSuffixWhitelistDraftInput"
@
keydown=
"handleRegistrationEmailSuffixWhitelistDraftKeydown"
@
blur=
"commitRegistrationEmailSuffixWhitelistDraft"
@
paste=
"handleRegistrationEmailSuffixWhitelistPaste"
/>
</div>
</div>
</div>
<p
class=
"mt-2 text-xs text-gray-500 dark:text-gray-400"
>
{{ t('admin.settings.registration.emailSuffixWhitelistInputHint') }}
</p>
</div>
<!-- Promo Code -->
<div
class=
"flex items-center justify-between border-t border-gray-100 pt-4 dark:border-dark-700"
...
...
@@ -1364,6 +1414,12 @@ import ImageUpload from '@/components/common/ImageUpload.vue'
import
{
useClipboard
}
from
'
@/composables/useClipboard
'
import
{
useAppStore
}
from
'
@/stores
'
import
{
useAdminSettingsStore
}
from
'
@/stores/adminSettings
'
import
{
isRegistrationEmailSuffixDomainValid
,
normalizeRegistrationEmailSuffixDomain
,
normalizeRegistrationEmailSuffixDomains
,
parseRegistrationEmailSuffixWhitelistInput
}
from
'
@/utils/registrationEmailPolicy
'
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
...
...
@@ -1375,6 +1431,9 @@ const saving = ref(false)
const
testingSmtp
=
ref
(
false
)
const
sendingTestEmail
=
ref
(
false
)
const
testEmailAddress
=
ref
(
''
)
const
logoError
=
ref
(
''
)
const
registrationEmailSuffixWhitelistTags
=
ref
<
string
[]
>
([])
const
registrationEmailSuffixWhitelistDraft
=
ref
(
''
)
// Admin API Key 状态
const
adminApiKeyLoading
=
ref
(
true
)
...
...
@@ -1414,6 +1473,7 @@ type SettingsForm = SystemSettings & {
const
form
=
reactive
<
SettingsForm
>
({
registration_enabled
:
true
,
email_verify_enabled
:
false
,
registration_email_suffix_whitelist
:
[],
promo_code_enabled
:
true
,
invitation_code_enabled
:
false
,
password_reset_enabled
:
false
,
...
...
@@ -1484,6 +1544,74 @@ const defaultSubscriptionGroupOptions = computed<DefaultSubscriptionGroupOption[
}))
)
const
registrationEmailSuffixWhitelistSeparatorKeys
=
new
Set
([
'
'
,
'
,
'
,
'
,
'
,
'
Enter
'
,
'
Tab
'
])
function
removeRegistrationEmailSuffixWhitelistTag
(
suffix
:
string
)
{
registrationEmailSuffixWhitelistTags
.
value
=
registrationEmailSuffixWhitelistTags
.
value
.
filter
(
(
item
)
=>
item
!==
suffix
)
}
function
addRegistrationEmailSuffixWhitelistTag
(
raw
:
string
)
{
const
suffix
=
normalizeRegistrationEmailSuffixDomain
(
raw
)
if
(
!
isRegistrationEmailSuffixDomainValid
(
suffix
)
||
registrationEmailSuffixWhitelistTags
.
value
.
includes
(
suffix
)
)
{
return
}
registrationEmailSuffixWhitelistTags
.
value
=
[
...
registrationEmailSuffixWhitelistTags
.
value
,
suffix
]
}
function
commitRegistrationEmailSuffixWhitelistDraft
()
{
if
(
!
registrationEmailSuffixWhitelistDraft
.
value
)
{
return
}
addRegistrationEmailSuffixWhitelistTag
(
registrationEmailSuffixWhitelistDraft
.
value
)
registrationEmailSuffixWhitelistDraft
.
value
=
''
}
function
handleRegistrationEmailSuffixWhitelistDraftInput
()
{
registrationEmailSuffixWhitelistDraft
.
value
=
normalizeRegistrationEmailSuffixDomain
(
registrationEmailSuffixWhitelistDraft
.
value
)
}
function
handleRegistrationEmailSuffixWhitelistDraftKeydown
(
event
:
KeyboardEvent
)
{
if
(
event
.
isComposing
)
{
return
}
if
(
registrationEmailSuffixWhitelistSeparatorKeys
.
has
(
event
.
key
))
{
event
.
preventDefault
()
commitRegistrationEmailSuffixWhitelistDraft
()
return
}
if
(
event
.
key
===
'
Backspace
'
&&
!
registrationEmailSuffixWhitelistDraft
.
value
&&
registrationEmailSuffixWhitelistTags
.
value
.
length
>
0
)
{
registrationEmailSuffixWhitelistTags
.
value
.
pop
()
}
}
function
handleRegistrationEmailSuffixWhitelistPaste
(
event
:
ClipboardEvent
)
{
const
text
=
event
.
clipboardData
?.
getData
(
'
text
'
)
||
''
if
(
!
text
.
trim
())
{
return
}
event
.
preventDefault
()
const
tokens
=
parseRegistrationEmailSuffixWhitelistInput
(
text
)
for
(
const
token
of
tokens
)
{
addRegistrationEmailSuffixWhitelistTag
(
token
)
}
}
// LinuxDo OAuth redirect URL suggestion
const
linuxdoRedirectUrlSuggestion
=
computed
(()
=>
{
if
(
typeof
window
===
'
undefined
'
)
return
''
...
...
@@ -1546,6 +1674,10 @@ async function loadSettings() {
validity_days
:
item
.
validity_days
}))
:
[]
registrationEmailSuffixWhitelistTags
.
value
=
normalizeRegistrationEmailSuffixDomains
(
settings
.
registration_email_suffix_whitelist
)
registrationEmailSuffixWhitelistDraft
.
value
=
''
form
.
smtp_password
=
''
form
.
turnstile_secret_key
=
''
form
.
linuxdo_connect_client_secret
=
''
...
...
@@ -1615,6 +1747,9 @@ async function saveSettings() {
const
payload
:
UpdateSettingsRequest
=
{
registration_enabled
:
form
.
registration_enabled
,
email_verify_enabled
:
form
.
email_verify_enabled
,
registration_email_suffix_whitelist
:
registrationEmailSuffixWhitelistTags
.
value
.
map
(
(
suffix
)
=>
`@
${
suffix
}
`
),
promo_code_enabled
:
form
.
promo_code_enabled
,
invitation_code_enabled
:
form
.
invitation_code_enabled
,
password_reset_enabled
:
form
.
password_reset_enabled
,
...
...
@@ -1660,6 +1795,10 @@ async function saveSettings() {
}
const
updated
=
await
adminAPI
.
settings
.
updateSettings
(
payload
)
Object
.
assign
(
form
,
updated
)
registrationEmailSuffixWhitelistTags
.
value
=
normalizeRegistrationEmailSuffixDomains
(
updated
.
registration_email_suffix_whitelist
)
registrationEmailSuffixWhitelistDraft
.
value
=
''
form
.
smtp_password
=
''
form
.
turnstile_secret_key
=
''
form
.
linuxdo_connect_client_secret
=
''
...
...
frontend/src/views/auth/EmailVerifyView.vue
View file @
bd0801a8
...
...
@@ -177,8 +177,13 @@ import Icon from '@/components/icons/Icon.vue'
import
TurnstileWidget
from
'
@/components/TurnstileWidget.vue
'
import
{
useAuthStore
,
useAppStore
}
from
'
@/stores
'
import
{
getPublicSettings
,
sendVerifyCode
}
from
'
@/api/auth
'
import
{
buildAuthErrorMessage
}
from
'
@/utils/authError
'
import
{
isRegistrationEmailSuffixAllowed
,
normalizeRegistrationEmailSuffixWhitelist
}
from
'
@/utils/registrationEmailPolicy
'
const
{
t
}
=
useI18n
()
const
{
t
,
locale
}
=
useI18n
()
// ==================== Router & Stores ====================
...
...
@@ -208,6 +213,7 @@ const hasRegisterData = ref<boolean>(false)
const
turnstileEnabled
=
ref
<
boolean
>
(
false
)
const
turnstileSiteKey
=
ref
<
string
>
(
''
)
const
siteName
=
ref
<
string
>
(
'
Sub2API
'
)
const
registrationEmailSuffixWhitelist
=
ref
<
string
[]
>
([])
// Turnstile for resend
const
turnstileRef
=
ref
<
InstanceType
<
typeof
TurnstileWidget
>
|
null
>
(
null
)
...
...
@@ -244,6 +250,9 @@ onMounted(async () => {
turnstileEnabled
.
value
=
settings
.
turnstile_enabled
turnstileSiteKey
.
value
=
settings
.
turnstile_site_key
||
''
siteName
.
value
=
settings
.
site_name
||
'
Sub2API
'
registrationEmailSuffixWhitelist
.
value
=
normalizeRegistrationEmailSuffixWhitelist
(
settings
.
registration_email_suffix_whitelist
||
[]
)
}
catch
(
error
)
{
console
.
error
(
'
Failed to load public settings:
'
,
error
)
}
...
...
@@ -306,6 +315,12 @@ async function sendCode(): Promise<void> {
errorMessage
.
value
=
''
try
{
if
(
!
isRegistrationEmailSuffixAllowed
(
email
.
value
,
registrationEmailSuffixWhitelist
.
value
))
{
errorMessage
.
value
=
buildEmailSuffixNotAllowedMessage
()
appStore
.
showError
(
errorMessage
.
value
)
return
}
const
response
=
await
sendVerifyCode
({
email
:
email
.
value
,
// 优先使用重发时新获取的 token(因为初始 token 可能已被使用)
...
...
@@ -320,15 +335,9 @@ async function sendCode(): Promise<void> {
showResendTurnstile
.
value
=
false
resendTurnstileToken
.
value
=
''
}
catch
(
error
:
unknown
)
{
const
err
=
error
as
{
message
?:
string
;
response
?:
{
data
?:
{
detail
?:
string
}
}
}
if
(
err
.
response
?.
data
?.
detail
)
{
errorMessage
.
value
=
err
.
response
.
data
.
detail
}
else
if
(
err
.
message
)
{
errorMessage
.
value
=
err
.
message
}
else
{
errorMessage
.
value
=
'
Failed to send verification code. Please try again.
'
}
errorMessage
.
value
=
buildAuthErrorMessage
(
error
,
{
fallback
:
'
Failed to send verification code. Please try again.
'
})
appStore
.
showError
(
errorMessage
.
value
)
}
finally
{
...
...
@@ -380,6 +389,12 @@ async function handleVerify(): Promise<void> {
isLoading
.
value
=
true
try
{
if
(
!
isRegistrationEmailSuffixAllowed
(
email
.
value
,
registrationEmailSuffixWhitelist
.
value
))
{
errorMessage
.
value
=
buildEmailSuffixNotAllowedMessage
()
appStore
.
showError
(
errorMessage
.
value
)
return
}
// Register with verification code
await
authStore
.
register
({
email
:
email
.
value
,
...
...
@@ -399,15 +414,9 @@ async function handleVerify(): Promise<void> {
// Redirect to dashboard
await
router
.
push
(
'
/dashboard
'
)
}
catch
(
error
:
unknown
)
{
const
err
=
error
as
{
message
?:
string
;
response
?:
{
data
?:
{
detail
?:
string
}
}
}
if
(
err
.
response
?.
data
?.
detail
)
{
errorMessage
.
value
=
err
.
response
.
data
.
detail
}
else
if
(
err
.
message
)
{
errorMessage
.
value
=
err
.
message
}
else
{
errorMessage
.
value
=
'
Verification failed. Please try again.
'
}
errorMessage
.
value
=
buildAuthErrorMessage
(
error
,
{
fallback
:
'
Verification failed. Please try again.
'
})
appStore
.
showError
(
errorMessage
.
value
)
}
finally
{
...
...
@@ -422,6 +431,19 @@ function handleBack(): void {
// Go back to registration
router
.
push
(
'
/register
'
)
}
function
buildEmailSuffixNotAllowedMessage
():
string
{
const
normalizedWhitelist
=
normalizeRegistrationEmailSuffixWhitelist
(
registrationEmailSuffixWhitelist
.
value
)
if
(
normalizedWhitelist
.
length
===
0
)
{
return
t
(
'
auth.emailSuffixNotAllowed
'
)
}
const
separator
=
String
(
locale
.
value
||
''
).
toLowerCase
().
startsWith
(
'
zh
'
)
?
'
、
'
:
'
,
'
return
t
(
'
auth.emailSuffixNotAllowedWithAllowed
'
,
{
suffixes
:
normalizedWhitelist
.
join
(
separator
)
})
}
</
script
>
<
style
scoped
>
...
...
frontend/src/views/auth/RegisterView.vue
View file @
bd0801a8
...
...
@@ -293,8 +293,13 @@ import Icon from '@/components/icons/Icon.vue'
import
TurnstileWidget
from
'
@/components/TurnstileWidget.vue
'
import
{
useAuthStore
,
useAppStore
}
from
'
@/stores
'
import
{
getPublicSettings
,
validatePromoCode
,
validateInvitationCode
}
from
'
@/api/auth
'
import
{
buildAuthErrorMessage
}
from
'
@/utils/authError
'
import
{
isRegistrationEmailSuffixAllowed
,
normalizeRegistrationEmailSuffixWhitelist
}
from
'
@/utils/registrationEmailPolicy
'
const
{
t
}
=
useI18n
()
const
{
t
,
locale
}
=
useI18n
()
// ==================== Router & Stores ====================
...
...
@@ -319,6 +324,7 @@ const turnstileEnabled = ref<boolean>(false)
const
turnstileSiteKey
=
ref
<
string
>
(
''
)
const
siteName
=
ref
<
string
>
(
'
Sub2API
'
)
const
linuxdoOAuthEnabled
=
ref
<
boolean
>
(
false
)
const
registrationEmailSuffixWhitelist
=
ref
<
string
[]
>
([])
// Turnstile
const
turnstileRef
=
ref
<
InstanceType
<
typeof
TurnstileWidget
>
|
null
>
(
null
)
...
...
@@ -370,6 +376,9 @@ onMounted(async () => {
turnstileSiteKey
.
value
=
settings
.
turnstile_site_key
||
''
siteName
.
value
=
settings
.
site_name
||
'
Sub2API
'
linuxdoOAuthEnabled
.
value
=
settings
.
linuxdo_oauth_enabled
registrationEmailSuffixWhitelist
.
value
=
normalizeRegistrationEmailSuffixWhitelist
(
settings
.
registration_email_suffix_whitelist
||
[]
)
// Read promo code from URL parameter only if promo code is enabled
if
(
promoCodeEnabled
.
value
)
{
...
...
@@ -557,6 +566,19 @@ function validateEmail(email: string): boolean {
return
emailRegex
.
test
(
email
)
}
function
buildEmailSuffixNotAllowedMessage
():
string
{
const
normalizedWhitelist
=
normalizeRegistrationEmailSuffixWhitelist
(
registrationEmailSuffixWhitelist
.
value
)
if
(
normalizedWhitelist
.
length
===
0
)
{
return
t
(
'
auth.emailSuffixNotAllowed
'
)
}
const
separator
=
String
(
locale
.
value
||
''
).
toLowerCase
().
startsWith
(
'
zh
'
)
?
'
、
'
:
'
,
'
return
t
(
'
auth.emailSuffixNotAllowedWithAllowed
'
,
{
suffixes
:
normalizedWhitelist
.
join
(
separator
)
}
)
}
function
validateForm
():
boolean
{
// Reset errors
errors
.
email
=
''
...
...
@@ -573,6 +595,11 @@ function validateForm(): boolean {
}
else
if
(
!
validateEmail
(
formData
.
email
))
{
errors
.
email
=
t
(
'
auth.invalidEmail
'
)
isValid
=
false
}
else
if
(
!
isRegistrationEmailSuffixAllowed
(
formData
.
email
,
registrationEmailSuffixWhitelist
.
value
)
)
{
errors
.
email
=
buildEmailSuffixNotAllowedMessage
()
isValid
=
false
}
// Password validation
...
...
@@ -694,15 +721,9 @@ async function handleRegister(): Promise<void> {
}
// Handle registration error
const
err
=
error
as
{
message
?:
string
;
response
?:
{
data
?:
{
detail
?:
string
}
}
}
if
(
err
.
response
?.
data
?.
detail
)
{
errorMessage
.
value
=
err
.
response
.
data
.
detail
}
else
if
(
err
.
message
)
{
errorMessage
.
value
=
err
.
message
}
else
{
errorMessage
.
value
=
t
(
'
auth.registrationFailed
'
)
}
errorMessage
.
value
=
buildAuthErrorMessage
(
error
,
{
fallback
:
t
(
'
auth.registrationFailed
'
)
}
)
// Also show error toast
appStore
.
showError
(
errorMessage
.
value
)
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment