Add e-mail based login

This commit is contained in:
2025-03-25 17:58:26 +01:00
parent 7aff2a2ead
commit 3fe0b43372
17 changed files with 499 additions and 305 deletions

View File

@@ -3,6 +3,7 @@ import { onMount } from "svelte";
import CopyButton from "../layout/CopyButton.svelte";
import Form from "./../util/Form.svelte";
import Button from "../layout/Button.svelte";
import OtpSetup from "./OTPSetup.svelte";
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + encodeURIComponent(window.user.username)
let affiliate_deny = false
@@ -28,16 +29,13 @@ let account_settings = {
please check your spam box too. Leave the field empty to remove
your current e-mail address from your account`,
separator: true
}, {
name: "password_old",
label: "Current password",
type: "current_password",
discription: `Enter your password here if you would like to change
your password.`
}, {
name: "password_new1",
label: "New password",
type: "new_password",
description: `Enter a new password here to change your account
password. If you do not wish to change your password, leave the
field empty`
}, {
name: "password_new2",
label: "New password again",
@@ -67,7 +65,6 @@ let account_settings = {
const form = new FormData()
form.append("email", fields.email)
form.append("password_old", fields.password_old)
form.append("password_new", fields.password_new1)
form.append("username", fields.username)
@@ -114,14 +111,29 @@ let delete_account = {
name: "description",
label: "Description",
type: "description",
description: `When you delete your pixeldrain account you will be
description: `
<p>
When you delete your pixeldrain account you will be
logged out on all of your devices. Your account will be
scheduled for deletion in seven days. If you log back in to your
account during those seven days the deletion will be canceled.
<br/><br/>
</p>
<p>
The files uploaded to your account are not deleted. You need to
do that manually before deleting the account. If you do not
delete your files then they will stay available as anonymously
uploaded files and they will follow the regular file expiry
rules.
</p>
<p>
Any prepaid credit on your account will also be deleted. This is
not recoverable. We don't offer refunds on prepaid credit.
</p>
<p>
If you have an active Pro subscription you need to end that
separately through your Patreon account. Deleting your
pixeldrain account will not cancel the subscription.`,
pixeldrain account will not cancel the subscription.
</p>`,
},
],
submit_red: true,
@@ -146,6 +158,11 @@ let delete_account = {
<Form config={account_settings}></Form>
</fieldset>
<fieldset>
<legend>Two-factor authentication</legend>
<OtpSetup/>
</fieldset>
<fieldset>
<legend>Affiliate settings</legend>
<Form config={affiliate_settings}></Form>

View File

@@ -37,7 +37,6 @@ const save_embed = async () => {
onMount(() => {
embed_domains = window.user.file_embed_domains
})
</script>
<LoadingIndicator loading={loading}/>

View File

@@ -0,0 +1,169 @@
<script lang="ts">
import { onMount } from "svelte";
import Button from "../layout/Button.svelte";
import CopyButton from "../layout/CopyButton.svelte";
import ToggleButton from "../layout/ToggleButton.svelte";
import { check_response, get_endpoint, get_user, type User } from "../lib/PixeldrainAPI.mjs";
let user: User = null
let secret = ""
let uri = ""
let qr = ""
let reveal_key = false
const generate_key = async () => {
let form = new FormData()
form.set("action", "generate")
const resp = await check_response(await fetch(
get_endpoint() + "/user/totp",
{method: "POST", body: form},
))
secret = resp.secret
uri = resp.uri
qr = get_endpoint()+"/misc/qr?text=" +encodeURIComponent(resp.uri)
console.log(resp)
}
let otp = ""
const verify = async (e: SubmitEvent) => {
e.preventDefault()
let form = new FormData()
form.set("action", "verify")
form.set("otp", otp)
form.set("secret", secret)
try {
const resp = await check_response(await fetch(
get_endpoint() + "/user/totp",
{method: "POST", body: form},
))
user.otp_enabled = true
alert("Success!")
} catch (err) {
if (err.value === "otp_incorrect") {
alert(
"The entered one-time password is not valid. It may have "+
"expired. Please return to your authenticator app and retry."+
"\n\n"+
"If it still doesn't work after that then your system clock "+
"might be incorrect. Please enable time synchronization in "+
"your operating system."
)
}
}
}
const disable = async () => {
let form = new FormData()
form.set("action", "delete")
await check_response(await fetch(
get_endpoint() + "/user/totp",
{method: "POST", body: form},
))
user.otp_enabled = false
}
onMount(async () => {
user = await get_user()
})
</script>
<div class="form">
{#if user !== null && user.otp_enabled}
<p>
Two-factor authentication is enabled on your account. Your account
is secure. If you have lost your recovery keys or authenticator
device, you can disable 2FA with the button below. After disabling
2FA you can enable it again.
<br/>
<Button click={disable} icon="close" label="Disable 2FA"/>
</p>
{:else if secret === ""}
<p>
You can improve your account security by enabling two-factor
authentication. When this is enabled you will be asked to enter a
second password when logging in. This password changes periodically.
</p>
<p>
Get started by generating an OTP key:
<Button click={generate_key} label="Generate OTP key"/>
</p>
{:else}
<h4>Key created</h4>
<p>
Now enter the secret in your Authenticator app. Most password
managers support one-time passwords. A popular option for Android is
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a>.
</p>
<p>
If your authenticator app has a QR code scanner you can scan the
code below. If not then you must enter the key manually.
</p>
<div class="qr_container">
<img src="{qr}" alt="OTP QR code" class="qr"/>
<div class="copy_container">
<Button click={() => window.location.href = uri} icon="key" label="Open in Authenticator app"/>
<CopyButton text={secret}>Copy secret key to clipboard</CopyButton>
<ToggleButton bind:on={reveal_key} icon_on="visibility" icon_off="visibility_off">Reveal secret key</ToggleButton>
{#if reveal_key}
<div class="key highlight_border">
{secret}
</div>
{/if}
</div>
</div>
<p>
Please save the secret key in your password manager or another safe
place. If you lose your authenticator app then the secret key is the
only way to gain access to your account.
</p>
<p>
Now enter the generated password to verify that the authenticator
app is working properly. This step enables two-factor authentication
on your account.
</p>
<form id="otp_verify" on:submit={verify} class="otp_form">
<input bind:value={otp} type="text" autocomplete="one-time-code" pattern={"[0-9]{6}"} required>
<Button form="otp_verify" label="Verify OTP"/>
</form>
{/if}
</div>
<style>
.qr_container {
display: flex;
flex-direction: row;
justify-content: space-around;
gap: 8px;
}
@media(max-width: 500px) {
.qr_container {
flex-direction: column;
align-items: center;
}
}
.qr {
flex: 1 1 auto;
max-width: 250px;
}
.copy_container {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.key {
line-break: anywhere;
}
.otp_form {
display: flex;
flex-direction: row;
}
.otp_form > input {
flex: 1 1 auto;
}
</style>