feat: send invitation code to backend

This commit is contained in:
Vlad Stan
2026-02-05 12:21:08 +02:00
parent 9648587850
commit 3b249fbad2
9 changed files with 117 additions and 19 deletions
+1
View File
@@ -318,6 +318,7 @@ class RegisterUser(BaseModel):
username: str = Query(default=..., min_length=2, max_length=20)
password: str = Query(default=..., min_length=8, max_length=50)
password_repeat: str = Query(default=..., min_length=8, max_length=50)
invitation_code: str | None = Query(default=None, min_length=1, max_length=256)
class CreateUser(BaseModel):
+18
View File
@@ -366,6 +366,9 @@ async def register(data: RegisterUser) -> JSONResponse:
if data.email and not is_valid_email_address(data.email):
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
if not await check_register_activation_settings(data):
raise HTTPException(HTTPStatus.FORBIDDEN, "User activation failed.")
account = Account(
id=uuid4().hex,
email=data.email,
@@ -376,6 +379,21 @@ async def register(data: RegisterUser) -> JSONResponse:
return _auth_success_response(account.username, account.id, account.email)
async def check_register_activation_settings(data: RegisterUser) -> bool:
if not settings.lnbits_require_user_activation:
return True
if settings.lnbits_user_activation_by_invitation_code and data.invitation_code:
code = data.invitation_code.strip()
if code == settings.lnbits_register_reusable_activation_code:
return True
if code in settings.lnbits_register_one_time_activation_codes:
settings.lnbits_register_one_time_activation_codes.remove(code)
return True
return False
@auth_router.put("/pubkey")
async def update_pubkey(
data: UpdateUserPubkey,
+10
View File
@@ -1197,6 +1197,12 @@ class PublicSettings(BaseModel):
wallet_featured_button_label: str | None = Field(alias="walletFeaturedButtonLabel")
wallet_featured_button_url: str | None = Field(alias="walletFeaturedButtonUrl")
wallet_featured_button_icon: str | None = Field(alias="walletFeaturedButtonIcon")
lnbits_user_activation_by_email: bool = Field(alias="userActivationByEmail")
lnbits_user_activation_by_nostr: bool = Field(alias="userActivationByNostr")
lnbits_user_activation_by_payment: bool = Field(alias="userActivationByPayment")
lnbits_user_activation_by_invitation_code: bool = Field(
alias="userActivationByInvitationCode"
)
@classmethod
def from_settings(cls, settings: Settings):
@@ -1248,6 +1254,10 @@ class PublicSettings(BaseModel):
walletFeaturedButtonLabel=settings.lnbits_wallet_featured_button_label,
walletFeaturedButtonUrl=settings.lnbits_wallet_featured_button_url,
walletFeaturedButtonIcon=settings.lnbits_wallet_featured_button_icon,
userActivationByEmail=settings.lnbits_user_activation_by_email,
userActivationByNostr=settings.lnbits_user_activation_by_nostr,
userActivationByPayment=settings.lnbits_user_activation_by_payment,
userActivationByInvitationCode=settings.lnbits_user_activation_by_invitation_code,
)
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -285,7 +285,7 @@ window.localisation.en = {
'Nip5 identifier to send notifications to',
notifications_nostr_identifiers: 'Nostr Identifiers',
notifications_nostr_identifiers_desc:
'List of identifiers to send notifications to',
'List of identifiers to send notifications to.',
notifications_telegram_config: 'Telegram Configuration',
notifications_enable_telegram: 'Enable Telegram',
@@ -724,6 +724,9 @@ window.localisation.en = {
confirmation_code_hint: 'The confirmation code that you have received.',
email: 'Email',
email_confirmation_hint: 'Email address to send the confirmation code to.',
nostr_identifier: 'Nostr Identifier',
nostr_identifier_hint:
'Nostr nip5 identifier or <npub> to send the confirmation code to.',
new_user_not_allowed: 'Registration is disabled.',
start_user_impersonation: 'Impersonate this user',
+30 -1
View File
@@ -434,6 +434,7 @@ window.app.component('username-password', {
password_2: String,
resetKey: String
},
data() {
return {
oauth: [
@@ -449,7 +450,8 @@ window.app.component('username-password', {
confirmationMethod: 'code',
confirmationEmail: '',
confirmationCode: '',
showConfirmationCode: false
showConfirmationCode: false,
nostrConfirmationIdentifier: ''
}
},
methods: {
@@ -553,6 +555,33 @@ window.app.component('username-password', {
computed: {
showOauth() {
return this.oauth.some(m => this.authMethods.includes(m))
},
disableRegister() {
const usernameOK = !!this.username
const passwordOK = !!this.password && this.password.length >= 8
const passwordsMatch = this.password === this.passwordRepeat
const codeOk =
this.confirmationMethodsCount === 0 ||
this.confirmationMethod !== 'code' ||
this.confirmationCode.length > 0
const nostrOk =
this.confirmationMethodsCount === 0 ||
this.confirmationMethod !== 'nostr' ||
this.nostrConfirmationIdentifier.length > 0
return (
!usernameOK || !passwordOK || !passwordsMatch || !codeOk || !nostrOk
)
},
confirmationMethodsCount() {
const methods = [
this.g.settings.userActivationByEmail,
this.g.settings.userActivationByNostr,
this.g.settings.userActivationByPayment,
this.g.settings.userActivationByInvitationCode
]
return methods.filter(Boolean).length
}
},
created() {}
+52 -14
View File
@@ -814,20 +814,54 @@ include('components/lnbits-error.vue') %}
type="password"
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
></q-input>
<div class="row justify-center q-mb-md">
<div
v-if="confirmationMethodsCount > 0"
class="q-my-md q-pa-sm text-body2 text-grey-4 bg-grey-9 rounded-borders"
>
<q-icon name="info" color="orange-4" class="q-mr-xs"></q-icon>
Confirmation required before you can log in.
<div v-if="confirmationMethodsCount > 1">
Please choose one of the methods below.
</div>
</div>
<div
v-if="confirmationMethodsCount > 1"
class="row justify-center q-mb-md"
>
<q-tabs
v-model="confirmationMethod"
dense
active-color="primary"
indicator-color="primary"
>
<q-tab name="code" icon="confirmation_number" label="Code"></q-tab>
<q-tab name="payment" icon="bolt" label="Payment"></q-tab>
<q-tab name="nostr" icon="bolt" label="Nostr"></q-tab>
<q-tab name="email" icon="email" label="Email"></q-tab>
<q-tab
v-if="g.settings.userActivationByInvitationCode"
name="code"
icon="confirmation_number"
label="Code"
></q-tab>
<q-tab
v-if="g.settings.userActivationByPayment"
name="payment"
icon="bolt"
label="Payment"
></q-tab>
<q-tab
v-if="g.settings.userActivationByNostr"
name="nostr"
icon="bolt"
label="Nostr"
></q-tab>
<q-tab
v-if="g.settings.userActivationByEmail"
name="email"
icon="email"
label="Email"
></q-tab>
</q-tabs>
</div>
<div>
<div v-if="confirmationMethodsCount > 0" class="q-mb-md">
<q-tab-panels v-model="confirmationMethod">
<q-tab-panel name="code" class="q-pa-none">
<div>
@@ -854,8 +888,17 @@ include('components/lnbits-error.vue') %}
<q-tab-panel name="payment">
<div>payment</div>
</q-tab-panel>
<q-tab-panel name="nostr">
<div>nostr</div>
<q-tab-panel name="nostr" class="q-pa-none">
<div>
<q-input
dense
filled
v-model="nostrConfirmationIdentifier"
:label="$t('nostr_identifier')"
:hint="$t('nostr_identifier_hint')"
>
</q-input>
</div>
</q-tab-panel>
<q-tab-panel name="email" class="q-pa-none">
<div>
@@ -876,12 +919,7 @@ include('components/lnbits-error.vue') %}
<q-btn
unelevated
color="primary"
:disable="
!password ||
!passwordRepeat ||
!username ||
password !== passwordRepeat
"
:disable="disableRegister"
type="submit"
class="full-width"
:label="$t('create_account')"
@@ -281,7 +281,6 @@
<div class="row items-center">
<q-toggle
size="md"
disable
:label="
formData.lnbits_user_activation_by_nostr
? $t('enabled')