✨ Advanced Authentication
This commit is contained in:
@@ -161,20 +161,29 @@
|
||||
{#await getUser()}
|
||||
<Spinner />
|
||||
{:then user}
|
||||
<div class="w-full">
|
||||
<div class="sm:flex space-x-7 md:items-start items-center">
|
||||
<div class="mb-4">
|
||||
<img
|
||||
class="rounded-md md:w-80"
|
||||
src="/api/v1/users/avatar"
|
||||
alt="Profile image of {user.username}"
|
||||
/>
|
||||
<div class="w-full grid grid-cols-6">
|
||||
<img
|
||||
class="rounded-md md:w-80"
|
||||
src="/api/v1/users/avatar"
|
||||
alt="Profile image of {user.username}"
|
||||
/>
|
||||
<div class="grid grid-rows-2 col-start-2 col-end-7">
|
||||
<div class="grid grid-cols-2">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold my-2">{user.username}</h1>
|
||||
<p class="text-lg mb-6 md:max-w-lg">
|
||||
{$t('words.email')}: {user.email}
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-4 flex justify-center">
|
||||
<a
|
||||
href="/account/settings/security"
|
||||
class="text-lg rounded-lg bg-[#B07156] p-2 hover:bg-opacity-80 transition h-fit m-auto"
|
||||
>Security-Settings</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold my-2">{user.username}</h1>
|
||||
<p class="text-lg mb-6 md:max-w-lg">
|
||||
{$t('words.email')}: {user.email}
|
||||
</p>
|
||||
<form class="flex flex-col md:flex-row" on:submit|preventDefault={changePassword}>
|
||||
<label
|
||||
>{$t('settings_page.old_password')}:<input
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { startRegistration } from '@simplewebauthn/browser';
|
||||
import TotpSetup from './totp_setup.svelte';
|
||||
import BackupCodes from './backup_codes.svelte';
|
||||
|
||||
let user_data: object | undefined;
|
||||
let security_keys: Array<{ id: number }> | undefined;
|
||||
let totp_activated: boolean | undefined;
|
||||
let totp_data;
|
||||
let backup_code;
|
||||
|
||||
const get_data = async () => {
|
||||
const res1 = await fetch('/api/v1/users/me');
|
||||
user_data = await res1.json();
|
||||
const res2 = await fetch('/api/v1/users/webauthn/list');
|
||||
security_keys = await res2.json();
|
||||
const res3 = await fetch('/api/v1/users/2fa/totp');
|
||||
totp_activated = (await res3.json()).activated;
|
||||
};
|
||||
let data = get_data();
|
||||
|
||||
const save_password_required = async () => {
|
||||
console.log(user_data?.require_password, 'here');
|
||||
if (!browser || user_data?.require_password === undefined) {
|
||||
return;
|
||||
}
|
||||
const res = await fetch('/api/v1/users/2fa/require_password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ require_password: user_data?.require_password })
|
||||
});
|
||||
user_data.require_password = (await res.json()).require_password;
|
||||
};
|
||||
|
||||
const add_security_key = async () => {
|
||||
const res1 = await fetch('/api/v1/users/webauthn/add_key');
|
||||
if (!res1.ok) {
|
||||
throw Error('Response not ok');
|
||||
}
|
||||
let attResp;
|
||||
const resp_data = await res1.json();
|
||||
try {
|
||||
resp_data.authenticatorSelection.authenticatorAttachment = 'cross-platform';
|
||||
for (let i = 0; i++; i < resp_data.excludeCredentials.length) {
|
||||
resp_data.excludeCredentials[i].transports = undefined;
|
||||
}
|
||||
attResp = await startRegistration(resp_data);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
await fetch('/api/v1/users/webauthn/add_key', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(attResp)
|
||||
});
|
||||
data = get_data();
|
||||
};
|
||||
|
||||
const remove_security_key = async (key_id: number) => {
|
||||
await fetch(`/api/v1/users/webauthn/key/${key_id}`, { method: 'DELETE' });
|
||||
data = get_data();
|
||||
};
|
||||
|
||||
const disable_totp = async () => {
|
||||
await fetch(`/api/v1/users/2fa/totp`, { method: 'DELETE' });
|
||||
data = get_data();
|
||||
};
|
||||
|
||||
const enable_totp = async () => {
|
||||
const res = await fetch('/api/v1/users/2fa/totp', { method: 'POST' });
|
||||
data = get_data();
|
||||
totp_data = await res.json();
|
||||
};
|
||||
|
||||
const get_backup_code = async () => {
|
||||
const res = await fetch('/api/v1/users/2fa/backup_code');
|
||||
backup_code = (await res.json()).code;
|
||||
};
|
||||
$: console.log(user_data?.require_password, 'hello');
|
||||
</script>
|
||||
|
||||
{#await data}
|
||||
<Spinner my_20={false} />
|
||||
{:then _}
|
||||
<div class="grid grid-rows-2 h-screen">
|
||||
<div class="grid grid-cols-2 h-full border-b-2 border-black">
|
||||
<div class="h-full w-full border-r-2 border-black">
|
||||
<h2 class="text-center text-2xl">Backup-Code</h2>
|
||||
<div class="flex h-full w-full justify-center">
|
||||
<button
|
||||
class="m-auto text-lg rounded-lg bg-[#B07156] p-4 hover:bg-opacity-80 transition"
|
||||
on:click={get_backup_code}
|
||||
>Get Backup-Codes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-full w-full">
|
||||
<h2 class="text-center text-2xl">Activate 2 Factor</h2>
|
||||
<div class="flex h-full w-full justify-center flex-col">
|
||||
<div class="m-auto">
|
||||
{#if user_data.require_password}
|
||||
<div class="flex items-center space-x-2">
|
||||
<button
|
||||
on:click={() => {
|
||||
user_data.require_password = !user_data.require_password;
|
||||
save_password_required();
|
||||
}}
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked="true"
|
||||
class="relative inline-flex h-5 w-8 shrink-0 cursor-pointer appearance-none rounded-full border-2 border-transparent bg-blue-700 transition focus:outline-none focus:ring focus:ring-blue-200"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none inline-block h-4 w-4 translate-x-3 rounded-full bg-white transition will-change-transform"
|
||||
/>
|
||||
</button>
|
||||
<span class="text-sm font-medium text-gray-700"
|
||||
>Two Factor authentication is activated</span
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
user_data.require_password = !user_data.require_password;
|
||||
save_password_required();
|
||||
}}
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
class="relative inline-flex h-5 w-8 shrink-0 cursor-pointer appearance-none rounded-full border-2 border-transparent bg-gray-200 transition focus:outline-none focus:ring focus:ring-blue-200"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none inline-block h-4 w-4 translate-x-0 rounded-full bg-white transition will-change-transform"
|
||||
/>
|
||||
</button>
|
||||
<span class="text-sm font-medium text-gray-700"
|
||||
>Two Factor authentication is deactivated</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 h-full">
|
||||
<div class="h-full w-full flex flex-col border-r-2 border-black">
|
||||
<h2 class="text-center text-2xl">Webauthn</h2>
|
||||
<div class="flex justify-center">
|
||||
{#if security_keys.length > 0}
|
||||
<p>Webauthn is available</p>
|
||||
{:else}
|
||||
<p>Webauthn is not available</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<button on:click={add_security_key}>Add Security-Key</button>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<ul class="list-disc block">
|
||||
{#each security_keys as key, i}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => {
|
||||
remove_security_key(key.id);
|
||||
}}
|
||||
class="hover:line-through transition">{i + 1}</button
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-full w-full flex flex-col">
|
||||
<h2 class="text-center text-2xl">Totp</h2>
|
||||
<div class="flex justify-center">
|
||||
{#if totp_activated}
|
||||
<p>Totp is available</p>
|
||||
{:else}
|
||||
<p>Totp is not available</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
{#if totp_activated}
|
||||
<button on:click={disable_totp}>Disable Totp</button>
|
||||
{:else}
|
||||
<button on:click={enable_totp}>Enable Totp</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
{#if totp_data}
|
||||
<TotpSetup bind:totp_data />
|
||||
{/if}
|
||||
|
||||
{#if backup_code}
|
||||
<BackupCodes bind:backup_code />
|
||||
{/if}
|
||||
@@ -0,0 +1,54 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let backup_code;
|
||||
|
||||
let already_downloaded = false;
|
||||
|
||||
const download_code = (force: boolean = false) => {
|
||||
if (already_downloaded && !force) {
|
||||
return;
|
||||
}
|
||||
const el = document.createElement('a');
|
||||
el.setAttribute('href', `data:text/plain;charset=utf-8,${backup_code}`);
|
||||
el.setAttribute('download', 'ClassQuiz-Backup-Code.txt');
|
||||
el.style.display = 'none';
|
||||
document.body.appendChild(el);
|
||||
el.click();
|
||||
document.body.removeChild(el);
|
||||
already_downloaded = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="w-screen h-screen fixed top-0 left-0 p-48 z-30 bg-black bg-opacity-50">
|
||||
<div class="w-full h-full">
|
||||
<button
|
||||
class="bg-gray-200 px-2 py-1 rounded-t-lg hover:bg-gray-300 transition"
|
||||
on:click={() => {
|
||||
backup_code = undefined;
|
||||
}}
|
||||
>Close
|
||||
</button>
|
||||
<div class="bg-white rounded-b-lg rounded-tr-lg w-full h-full flex flex-col">
|
||||
<h2 class="text-3xl m-auto">Your Backup-Code</h2>
|
||||
<p
|
||||
class="select-all font-mono text-xl m-auto"
|
||||
on:click={() => {
|
||||
download_code(false);
|
||||
}}
|
||||
>
|
||||
{backup_code}
|
||||
</p>
|
||||
<p class="m-auto">Save this somewhere safe!</p>
|
||||
<button
|
||||
on:click={() => {
|
||||
download_code(true);
|
||||
}}
|
||||
class="m-auto p-2 bg-[#B07156] rounded-lg">Download code</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import QRCode from 'qrcode';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
|
||||
export let totp_data: { url: string; secret: string } | undefined;
|
||||
|
||||
const get_image_url = async () => {
|
||||
return await QRCode.toDataURL(totp_data.url);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="w-screen h-screen fixed top-0 left-0 p-48 z-30 bg-black bg-opacity-50">
|
||||
<div class="w-full h-full">
|
||||
<button
|
||||
class="bg-gray-200 px-2 py-1 rounded-t-lg hover:bg-gray-300 transition"
|
||||
on:click={() => {
|
||||
totp_data = undefined;
|
||||
}}
|
||||
>Close
|
||||
</button>
|
||||
<div class="bg-white rounded-b-lg rounded-tr-lg w-full h-full">
|
||||
<div class="grid grid-cols-3 w-full h-full">
|
||||
<div class="flex flex-col justify-center w-full h-5/6">
|
||||
<span class="m-auto" />
|
||||
<div class="h-5/6 flex">
|
||||
<p class="my-auto ml-auto">Scan this to set up the code</p>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<p class="my-auto ml-auto">
|
||||
Enter this as the secret if you can't scan the code
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start w-full h-5/6">
|
||||
<h2 class="text-2xl m-auto">Totp-Setup</h2>
|
||||
{#await get_image_url()}
|
||||
<Spinner my_20={false} />
|
||||
{:then data}
|
||||
<div class="m-auto h-5/6 object-contain w-full">
|
||||
<img
|
||||
src={data}
|
||||
alt="QR-Code for Totp-setup"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
{/await}
|
||||
<p class="m-auto select-all font-mono">{totp_data.secret}</p>
|
||||
</div>
|
||||
<div class="flex justify-center h-5/6 w-full">
|
||||
<p class="m-auto text-3xl p-4">Do not forget to save your recovery-code!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user