* feat: add password reset for usermanager - add a reset_key to account table - add ?reset_key= GET arguments to index.html and show reset form if provided - superuser can generate and copy reset url with key to share future ideas: - could add send forgot password email if user fill out email address * feat: simplify reset key * test: use reset key * test: add more tests * test: reset passwords do not match * test: `reset_password_auth_threshold_expired` --------- Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
660 lines
22 KiB
HTML
660 lines
22 KiB
HTML
{% extends "public.html" %} {% block scripts %}
|
|
<script src="{{ static_url_for('static', 'js/index.js') }}"></script>
|
|
{% endblock %} {% block page %}
|
|
<div class="row q-col-gutter-md justify-center">
|
|
<div
|
|
v-if="isUserAuthorized"
|
|
class="col-12 col-md-6 col-lg-6 q-gutter-y-md"
|
|
></div>
|
|
<div v-else class="col-12 col-md-4 col-lg-4 q-gutter-y-md">
|
|
<div class="gt-sm">
|
|
<h3
|
|
class="q-my-none"
|
|
v-if="'{{LNBITS_SHOW_HOME_PAGE_ELEMENTS}}' == 'True'"
|
|
>
|
|
{{SITE_TITLE}}
|
|
</h3>
|
|
<h5 class="q-my-md" v-if="'{{LNBITS_SHOW_HOME_PAGE_ELEMENTS}}' == 'True'">
|
|
{{SITE_TAGLINE}}
|
|
</h5>
|
|
<div
|
|
v-html="formatDescription"
|
|
v-if="'{{LNBITS_SHOW_HOME_PAGE_ELEMENTS}}' == 'True'"
|
|
></div>
|
|
</div>
|
|
{% if lnurl and LNBITS_NEW_ACCOUNTS_ALLOWED and ("user-id-only" in
|
|
LNBITS_AUTH_METHODS)%}
|
|
<div class="row q-mt-xl">
|
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
@click="processing"
|
|
type="a"
|
|
href="/lnurlwallet?lightning={{ lnurl }}"
|
|
v-text="$t('press_to_claim')"
|
|
class="full-width"
|
|
></q-btn>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
{%else%} {% endif %}
|
|
|
|
<div class="row q-mt-md">
|
|
<div class="col-12 col-md-10 col-lg-10 q-gutter-y-md">
|
|
<q-badge v-if="isAccessTokenExpired" color="primary" rounded>
|
|
<div class="text-h5">
|
|
<span v-text="$t('session_has_expired')"></span>
|
|
</div>
|
|
</q-badge>
|
|
<q-card class="shadow-12">
|
|
{% if "user-id-only" in LNBITS_AUTH_METHODS %}
|
|
<q-card-section>
|
|
<div class="text-h6">
|
|
<span v-text="$t('instant_access_question')"></span>
|
|
<br />
|
|
<q-badge
|
|
@click="showLogin('user-id-only')"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
rounded
|
|
>
|
|
<strong>
|
|
<q-icon name="account_circle" size="xs"></q-icon>
|
|
<span v-text="$t('login_with_user_id')"></span> </strong
|
|
></q-badge>
|
|
{% if LNBITS_NEW_ACCOUNTS_ALLOWED %}
|
|
<span v-text="$t('or')"></span>
|
|
<q-badge
|
|
@click="showRegister('user-id-only')"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
rounded
|
|
>
|
|
<strong>
|
|
<q-icon name="add" size="xs"></q-icon>
|
|
<span v-text="$t('create_new_wallet')"></span>
|
|
</strong>
|
|
</q-badge>
|
|
{%endif%}
|
|
</div>
|
|
</q-card-section>
|
|
<q-separator></q-separator>
|
|
<q-card-section
|
|
v-if="authAction === 'login' && authMethod === 'user-id-only'"
|
|
>
|
|
<b> <span v-text="$t('login_with_user_id')"></span> </b><br /><br />
|
|
<q-form @submit="loginUsr" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="usr"
|
|
label="usr"
|
|
type="password"
|
|
></q-input>
|
|
<div>
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
:disable="usr == ''"
|
|
type="submit"
|
|
label="Login"
|
|
class="full-width"
|
|
></q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card-section>
|
|
{%endif%} {% if "username-password" in LNBITS_AUTH_METHODS %}
|
|
<q-card-section
|
|
v-if="authAction === 'login' && authMethod === 'username-password'"
|
|
>
|
|
<div class="q-mb-lg">
|
|
<strong>
|
|
<span v-text="$t('login_to_account')"></span>
|
|
</strong>
|
|
</div>
|
|
<q-form @submit="login" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="username"
|
|
name="username"
|
|
:label="$t('username_or_email') + ' *'"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="password"
|
|
name="password"
|
|
:label="$t('password') + ' *'"
|
|
type="password"
|
|
></q-input>
|
|
<div>
|
|
<q-btn
|
|
:disable="!username || !password"
|
|
color="primary"
|
|
type="submit"
|
|
:label="$t('login')"
|
|
class="full-width"
|
|
></q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card-section>
|
|
<q-card-section
|
|
v-if="authAction === 'register' && authMethod === 'username-password'"
|
|
>
|
|
<b> <span v-text="$t('create_account')"></span> </b><br /><br />
|
|
<q-form @submit="register" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
required
|
|
v-model="username"
|
|
:label="$t('username') + ' *'"
|
|
:rules="[(val) => validateUsername(val) || $t('invalid_username')]"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="password"
|
|
:label="$t('password') + ' *'"
|
|
type="password"
|
|
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="passwordRepeat"
|
|
:label="$t('password_repeat') + ' *'"
|
|
type="password"
|
|
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
|
|
></q-input>
|
|
<div>
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
:disable="!password || !passwordRepeat|| !username || (password !== passwordRepeat)"
|
|
type="submit"
|
|
class="full-width"
|
|
:label="$t('create_account')"
|
|
></q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card-section>
|
|
<q-card-section
|
|
v-if="authAction === 'reset' && authMethod === 'username-password'"
|
|
>
|
|
<b> <span v-text="$t('reset_password')"></span> </b><br /><br />
|
|
<q-form @submit="reset" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
required
|
|
:disable="true"
|
|
v-model="reset_key"
|
|
:label="$t('reset_key') + ' *'"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="password"
|
|
:label="$t('password') + ' *'"
|
|
type="password"
|
|
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="passwordRepeat"
|
|
:label="$t('password_repeat') + ' *'"
|
|
type="password"
|
|
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
|
|
></q-input>
|
|
<div>
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
:disable="!password || !passwordRepeat|| !reset_key || (password !== passwordRepeat)"
|
|
type="submit"
|
|
class="full-width"
|
|
:label="$t('reset_password')"
|
|
></q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card-section>
|
|
{%endif%} {% if LNBITS_NEW_ACCOUNTS_ALLOWED %}
|
|
<q-card-section
|
|
v-if="authAction === 'register' && authMethod === 'user-id-only'"
|
|
>
|
|
<div>
|
|
<q-form @submit="createWallet" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="walletName"
|
|
:label='$t("name_your_wallet", { name: "{{ SITE_TITLE }} *" })'
|
|
></q-input>
|
|
<div>
|
|
<q-btn
|
|
color="primary"
|
|
:disable="walletName == ''"
|
|
type="submit"
|
|
:label="$t('add_wallet')"
|
|
class="full-width"
|
|
></q-btn>
|
|
</div>
|
|
</q-form>
|
|
</div>
|
|
</q-card-section>
|
|
{% if "username-password" in LNBITS_AUTH_METHODS %}
|
|
<q-card-section
|
|
v-if="authAction === 'login' && authMethod === 'username-password'"
|
|
>
|
|
<div>
|
|
<q-btn
|
|
color="grey"
|
|
outline
|
|
:label="$t('register')"
|
|
class="full-width"
|
|
@click="showRegister('username-password')"
|
|
></q-btn>
|
|
</div>
|
|
</q-card-section>
|
|
{%endif%}
|
|
<q-separator></q-separator>
|
|
{% endif %}
|
|
|
|
<q-card-section
|
|
v-if="authAction === 'login' && authMethod === 'username-password'"
|
|
>
|
|
<div class="row">
|
|
{% if "nostr-auth-nip98" in LNBITS_AUTH_METHODS %}
|
|
<div class="col-12 full-width q-pa-sm">
|
|
<q-btn
|
|
@click="signInWithNostr"
|
|
outline
|
|
no-caps
|
|
rounded
|
|
color="grey"
|
|
class="full-width"
|
|
>
|
|
<q-avatar size="32px" class="q-mr-md">
|
|
<q-img
|
|
class="bg-primary"
|
|
:src="'{{ static_url_for('static', 'images/logos/nostr.svg') }}'"
|
|
></q-img>
|
|
</q-avatar>
|
|
<div>
|
|
<span v-text="$t('signin_with_nostr')"></span>
|
|
</div>
|
|
</q-btn>
|
|
</div>
|
|
{%endif%} {% if "google-auth" in LNBITS_AUTH_METHODS %}
|
|
<div class="col-12 full-width q-pa-sm">
|
|
<q-btn
|
|
href="/api/v1/auth/google"
|
|
type="a"
|
|
outline
|
|
no-caps
|
|
rounded
|
|
color="grey"
|
|
class="full-width"
|
|
>
|
|
<q-avatar size="32px" class="q-mr-md">
|
|
<q-img
|
|
:src="'{{ static_url_for('static', 'images/google-logo.png') }}'"
|
|
></q-img>
|
|
</q-avatar>
|
|
<div>
|
|
<span v-text="$t('signin_with_google')"></span>
|
|
</div>
|
|
</q-btn>
|
|
</div>
|
|
{%endif%} {% if "github-auth" in LNBITS_AUTH_METHODS %}
|
|
<div class="col-12 full-width q-pa-sm">
|
|
<q-btn
|
|
href="/api/v1/auth/github"
|
|
type="a"
|
|
outline
|
|
no-caps
|
|
color="grey"
|
|
rounded
|
|
class="full-width"
|
|
>
|
|
<q-avatar size="32px" class="q-mr-md">
|
|
<q-img
|
|
:src="'{{ static_url_for('static', 'images/github-logo.png') }}'"
|
|
></q-img>
|
|
</q-avatar>
|
|
<div><span v-text="$t('signin_with_github')"></span></div>
|
|
</q-btn>
|
|
</div>
|
|
{%endif%} {% if "keycloak-auth" in LNBITS_AUTH_METHODS %}
|
|
<div class="col-12 full-width q-pa-sm">
|
|
<q-btn
|
|
href="/api/v1/auth/keycloak"
|
|
type="a"
|
|
outline
|
|
no-caps
|
|
color="grey"
|
|
rounded
|
|
class="full-width"
|
|
>
|
|
<q-avatar size="32px" class="q-mr-md">
|
|
<q-img
|
|
:src="'{{ static_url_for('static', 'images/keycloak-logo.png') }}'"
|
|
></q-img>
|
|
</q-avatar>
|
|
<div><span v-text="$t('signin_with_keycloak')"></span></div>
|
|
</q-btn>
|
|
</div>
|
|
{%endif%}
|
|
</div>
|
|
</q-card-section>
|
|
<q-card-section v-else>
|
|
<div class="row">
|
|
<div class="col q-pa-sm">
|
|
<q-btn
|
|
@click="showLogin('username-password')"
|
|
:label="$t('back')"
|
|
outline
|
|
rounded
|
|
color="grey"
|
|
class="full-width"
|
|
>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="'{{LNBITS_SHOW_HOME_PAGE_ELEMENTS}}' == 'False'"
|
|
class="col-12 col-md-5 col-lg-5 q-pt-xl"
|
|
>
|
|
<h3 class="q-my-none">{{SITE_TITLE}}</h3>
|
|
<h5 class="q-my-md">{{SITE_TAGLINE}}</h5>
|
|
<div v-html="formatDescription"></div>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3 col-lg-3 gt-sm" v-else>
|
|
<div class="row q-col-gutter-lg justify-center">
|
|
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
|
|
<q-btn
|
|
outline
|
|
color="grey"
|
|
type="a"
|
|
href="https://github.com/lnbits/lnbits"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
:label="$t('view_github')"
|
|
class="full-width"
|
|
></q-btn>
|
|
<q-btn
|
|
outline
|
|
color="grey"
|
|
type="a"
|
|
href="https://demo.lnbits.com/lnurlp/link/fH59GD"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
:label="$t('donate')"
|
|
class="full-width q-mb-lg"
|
|
></q-btn>
|
|
</div>
|
|
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
|
|
<q-btn
|
|
flat
|
|
color="secondary"
|
|
:label="$t('runs_on')"
|
|
class="full-width"
|
|
></q-btn>
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://github.com/ElementsProject/lightning"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/cln.png') }}' : '{{ static_url_for('static', 'images/clnl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a
|
|
href="https://github.com/lightningnetwork/lnd"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/lnd.png') }}' : '{{ static_url_for('static', 'images/lnd.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://opennode.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/opennode.png') }}' : '{{ static_url_for('static', 'images/opennodel.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a
|
|
href="https://lnpay.co/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/lnpay.png') }}' : '{{ static_url_for('static', 'images/lnpayl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://github.com/rootzoll/raspiblitz"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/blitz.png') }}' : '{{ static_url_for('static', 'images/blitzl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a
|
|
href="https://start9.com/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/start9.png') }}' : '{{ static_url_for('static', 'images/start9l.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://getumbrel.com/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/umbrel.png') }}' : '{{ static_url_for('static', 'images/umbrell.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a
|
|
href="https://mynodebtc.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/mynode.png') }}' : '{{ static_url_for('static', 'images/mynodel.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://github.com/shesek/spark-wallet"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/spark.png') }}' : '{{ static_url_for('static', 'images/sparkl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a
|
|
href="https://voltage.cloud"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/voltage.png') }}' : '{{ static_url_for('static', 'images/voltagel.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://breez.technology/sdk/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/breez.png') }}' : '{{ static_url_for('static', 'images/breezl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a
|
|
href="https://blockstream.com/lightning/greenlight/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/greenlight.png') }}' : '{{ static_url_for('static', 'images/greenlightl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://getalby.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/alby.png') }}' : '{{ static_url_for('static', 'images/albyl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col q-pl-md">
|
|
<a href="https://zbd.gg" target="_blank" rel="noopener noreferrer">
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/zbd.png') }}' : '{{ static_url_for('static', 'images/zbdl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<a
|
|
href="https://phoenix.acinq.co/server"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/phoenixd.png') }}' : '{{ static_url_for('static', 'images/phoenixdl.png') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
<div class="col">
|
|
<a
|
|
href="https://boltz.exchange/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<q-img
|
|
contain
|
|
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/boltz.svg') }}' : '{{ static_url_for('static', 'images/boltz.svg') }}'"
|
|
></q-img>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% if AD_SPACE %}
|
|
<div class="row justify-center">
|
|
<q-btn flat color="secondary" class="full-width q-mb-md"
|
|
>{{ AD_SPACE_TITLE }}</q-btn
|
|
>
|
|
|
|
{% for ADS in AD_SPACE %} {% set AD = ADS.split(';') %}
|
|
<div class="flex flex-center column">
|
|
<a href="{{ AD[0] }}">
|
|
<img
|
|
v-if="($q.dark.isActive)"
|
|
src="{{ AD[1] }}"
|
|
style="max-width: 420px"
|
|
/>
|
|
<img v-else src="{{ AD[2] }}" style="max-width: 420px" />
|
|
</a>
|
|
</div>
|
|
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div
|
|
v-if="'{{LNBITS_SHOW_HOME_PAGE_ELEMENTS}}' == 'True'"
|
|
class="row gt-sm q-mt-xl"
|
|
>
|
|
<div class="col-1"></div>
|
|
<div class="col-10 q-pl-xl">
|
|
<span v-text="$t('lnbits_description')"></span>
|
|
</div>
|
|
<div class="col-1"></div>
|
|
</div>
|
|
{% endblock %}
|