feat: send invitation code to backend

This commit is contained in:
Vlad Stan
2026-02-03 18:11:04 +02:00
parent 7db645d388
commit 84a958af6e
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) username: str = Query(default=..., min_length=2, max_length=20)
password: str = Query(default=..., min_length=8, max_length=50) password: str = Query(default=..., min_length=8, max_length=50)
password_repeat: 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): 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): if data.email and not is_valid_email_address(data.email):
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid 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( account = Account(
id=uuid4().hex, id=uuid4().hex,
email=data.email, email=data.email,
@@ -376,6 +379,21 @@ async def register(data: RegisterUser) -> JSONResponse:
return _auth_success_response(account.username, account.id, account.email) 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") @auth_router.put("/pubkey")
async def update_pubkey( async def update_pubkey(
data: UpdateUserPubkey, data: UpdateUserPubkey,
+10
View File
@@ -1197,6 +1197,12 @@ class PublicSettings(BaseModel):
wallet_featured_button_label: str | None = Field(alias="walletFeaturedButtonLabel") wallet_featured_button_label: str | None = Field(alias="walletFeaturedButtonLabel")
wallet_featured_button_url: str | None = Field(alias="walletFeaturedButtonUrl") wallet_featured_button_url: str | None = Field(alias="walletFeaturedButtonUrl")
wallet_featured_button_icon: str | None = Field(alias="walletFeaturedButtonIcon") 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 @classmethod
def from_settings(cls, settings: Settings): def from_settings(cls, settings: Settings):
@@ -1248,6 +1254,10 @@ class PublicSettings(BaseModel):
walletFeaturedButtonLabel=settings.lnbits_wallet_featured_button_label, walletFeaturedButtonLabel=settings.lnbits_wallet_featured_button_label,
walletFeaturedButtonUrl=settings.lnbits_wallet_featured_button_url, walletFeaturedButtonUrl=settings.lnbits_wallet_featured_button_url,
walletFeaturedButtonIcon=settings.lnbits_wallet_featured_button_icon, 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', 'Nip5 identifier to send notifications to',
notifications_nostr_identifiers: 'Nostr Identifiers', notifications_nostr_identifiers: 'Nostr Identifiers',
notifications_nostr_identifiers_desc: notifications_nostr_identifiers_desc:
'List of identifiers to send notifications to', 'List of identifiers to send notifications to.',
notifications_telegram_config: 'Telegram Configuration', notifications_telegram_config: 'Telegram Configuration',
notifications_enable_telegram: 'Enable Telegram', notifications_enable_telegram: 'Enable Telegram',
@@ -724,6 +724,9 @@ window.localisation.en = {
confirmation_code_hint: 'The confirmation code that you have received.', confirmation_code_hint: 'The confirmation code that you have received.',
email: 'Email', email: 'Email',
email_confirmation_hint: 'Email address to send the confirmation code to.', 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.', new_user_not_allowed: 'Registration is disabled.',
start_user_impersonation: 'Impersonate this user', start_user_impersonation: 'Impersonate this user',
+30 -1
View File
@@ -434,6 +434,7 @@ window.app.component('username-password', {
password_2: String, password_2: String,
resetKey: String resetKey: String
}, },
data() { data() {
return { return {
oauth: [ oauth: [
@@ -449,7 +450,8 @@ window.app.component('username-password', {
confirmationMethod: 'code', confirmationMethod: 'code',
confirmationEmail: '', confirmationEmail: '',
confirmationCode: '', confirmationCode: '',
showConfirmationCode: false showConfirmationCode: false,
nostrConfirmationIdentifier: ''
} }
}, },
methods: { methods: {
@@ -553,6 +555,33 @@ window.app.component('username-password', {
computed: { computed: {
showOauth() { showOauth() {
return this.oauth.some(m => this.authMethods.includes(m)) 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() {} created() {}
+52 -14
View File
@@ -814,20 +814,54 @@ include('components/lnbits-error.vue') %}
type="password" type="password"
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]" :rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
></q-input> ></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 <q-tabs
v-model="confirmationMethod" v-model="confirmationMethod"
dense dense
active-color="primary" active-color="primary"
indicator-color="primary" indicator-color="primary"
> >
<q-tab name="code" icon="confirmation_number" label="Code"></q-tab> <q-tab
<q-tab name="payment" icon="bolt" label="Payment"></q-tab> v-if="g.settings.userActivationByInvitationCode"
<q-tab name="nostr" icon="bolt" label="Nostr"></q-tab> name="code"
<q-tab name="email" icon="email" label="Email"></q-tab> 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> </q-tabs>
</div> </div>
<div> <div v-if="confirmationMethodsCount > 0" class="q-mb-md">
<q-tab-panels v-model="confirmationMethod"> <q-tab-panels v-model="confirmationMethod">
<q-tab-panel name="code" class="q-pa-none"> <q-tab-panel name="code" class="q-pa-none">
<div> <div>
@@ -854,8 +888,17 @@ include('components/lnbits-error.vue') %}
<q-tab-panel name="payment"> <q-tab-panel name="payment">
<div>payment</div> <div>payment</div>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="nostr"> <q-tab-panel name="nostr" class="q-pa-none">
<div>nostr</div> <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>
<q-tab-panel name="email" class="q-pa-none"> <q-tab-panel name="email" class="q-pa-none">
<div> <div>
@@ -876,12 +919,7 @@ include('components/lnbits-error.vue') %}
<q-btn <q-btn
unelevated unelevated
color="primary" color="primary"
:disable=" :disable="disableRegister"
!password ||
!passwordRepeat ||
!username ||
password !== passwordRepeat
"
type="submit" type="submit"
class="full-width" class="full-width"
:label="$t('create_account')" :label="$t('create_account')"
@@ -281,7 +281,6 @@
<div class="row items-center"> <div class="row items-center">
<q-toggle <q-toggle
size="md" size="md"
disable
:label=" :label="
formData.lnbits_user_activation_by_nostr formData.lnbits_user_activation_by_nostr
? $t('enabled') ? $t('enabled')