half login flow is working
This commit is contained in:
parent
0c1e843f08
commit
f72cee6774
|
@ -0,0 +1,22 @@
|
||||||
|
export function setCookie(name: string, value: string, expires: number) {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(d.getTime() + expires);
|
||||||
|
const expires_at = "expires="+ d.toUTCString();
|
||||||
|
document.cookie = name + "=" + value + ";" + expires_at + ";path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name: string): string {
|
||||||
|
const name_with_equals = name + "=";
|
||||||
|
const decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
const ca = decodedCookie.split(';');
|
||||||
|
for(let i = 0; i <ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name_with_equals) == 0) {
|
||||||
|
return c.substring(name_with_equals.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
"prompt": "What's your email?",
|
"prompt": "What's your email?",
|
||||||
"actionButtonText": "Log in",
|
"actionButtonText": "Log in",
|
||||||
"createaccount": "create an account",
|
"createaccount": "create an account",
|
||||||
|
"sentMagicLink": "Check your email!",
|
||||||
|
"magicLinkExplainer": "We sent you a link, click on it to continue logging in.",
|
||||||
"apierror": {
|
"apierror": {
|
||||||
"authorization was provided but it is expired or invalid": "User does not exist, maybe consider creating an account?",
|
"authorization was provided but it is expired or invalid": "User does not exist, maybe consider creating an account?",
|
||||||
"xhrerror": "unable to contact server, please try again later"
|
"xhrerror": "unable to contact server, please try again later"
|
||||||
|
@ -34,6 +36,16 @@
|
||||||
|
|
||||||
"magiclink": {
|
"magiclink": {
|
||||||
"loadtitle": "Verifying magic link...",
|
"loadtitle": "Verifying magic link...",
|
||||||
"loadsubtitle": "Hang on while we check this link."
|
"loadsubtitle": "Hang on while we check this link.",
|
||||||
|
"failed": "Unable to verify magic link",
|
||||||
|
"finished": "Magic link verified!",
|
||||||
|
"redirecting": "Redirecting...",
|
||||||
|
"missing_email": "Your request didn't contain your email. Make sure you have the entire link.",
|
||||||
|
"missing_token": "Your request didn't contain your auth token. Make sure you have the entire link.",
|
||||||
|
"tryAgain": "Try logging in again?",
|
||||||
|
"apierror": {
|
||||||
|
"unable to parse the request body, is it properly formatted?": "There was an error processing your request, please try again later.",
|
||||||
|
"this token is invalid - no rows returned by a query that expected to return at least one row": "This token is invalid or has expired."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -64,7 +64,7 @@
|
||||||
<form class="mt-5" action="#" method="POST" on:submit|preventDefault={generateMagicLink}>
|
<form class="mt-5" action="#" method="POST" on:submit|preventDefault={generateMagicLink}>
|
||||||
<div class="-space-y-px rounded-md shadow-sm">
|
<div class="-space-y-px rounded-md shadow-sm">
|
||||||
<label for="email" class="sr-only">{t('login.prompt')}</label>
|
<label for="email" class="sr-only">{t('login.prompt')}</label>
|
||||||
<input id="email"
|
<input bind:value={email} id="email"
|
||||||
class="dark:bg-slate-500 bg-gray-200 w-full rounded px-3 py-2 focus:outline-none focus:ring-purple-500 appearance-none">
|
class="dark:bg-slate-500 bg-gray-200 w-full rounded px-3 py-2 focus:outline-none focus:ring-purple-500 appearance-none">
|
||||||
{#if hasError}
|
{#if hasError}
|
||||||
<span class="text-red-600 text-sm">{error}</span>
|
<span class="text-red-600 text-sm">{error}</span>
|
||||||
|
|
|
@ -1,8 +1,78 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {t} from "$lib/i18n";
|
import {t} from "$lib/i18n";
|
||||||
import {API_ROOT} from "$lib/config";
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {setCookie} from "$lib/cookie";
|
||||||
|
|
||||||
let isLoading = true;
|
let isLoading = true;
|
||||||
|
let hasError = false;
|
||||||
|
let error = "";
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// content loaded - start checking the api
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (!urlParams.has("email")) {
|
||||||
|
error = t('magiclink.missing_email');
|
||||||
|
hasError = true;
|
||||||
|
isLoading = false;
|
||||||
|
return;
|
||||||
|
} else if (!urlParams.has("token")) {
|
||||||
|
error = t('magiclink.missing_token');
|
||||||
|
hasError = true;
|
||||||
|
isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = urlParams.get("token");
|
||||||
|
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', `${API_ROOT}/v1/auth/verify-magic-link`);
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json");
|
||||||
|
xhr.send(JSON.stringify({
|
||||||
|
magicLinkToken: token
|
||||||
|
}));
|
||||||
|
|
||||||
|
xhr.ontimeout = () => {
|
||||||
|
hasError = true;
|
||||||
|
error = t('magiclink.apierror.timeout');
|
||||||
|
isLoading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
// error
|
||||||
|
hasError = true;
|
||||||
|
const rawerror = JSON.parse(xhr.responseText).errors[0].message;
|
||||||
|
|
||||||
|
error = t(`magiclink.apierror.${rawerror}`);
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
} else {
|
||||||
|
let resp = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
|
if (resp.data === undefined || resp.data.sessionToken === undefined) {
|
||||||
|
error = t("magiclink.apierror.badresponse");
|
||||||
|
hasError = true;
|
||||||
|
isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sess = resp.data.sessionToken;
|
||||||
|
// Set a really, really long expiry date. We will remove it when the server starts giving us unauthorized errors, but we don't know ahead of time what the server's expiration time is.
|
||||||
|
setCookie("sessionToken", sess, (86400 * 365));
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
hasError = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = () => {
|
||||||
|
hasError = true;
|
||||||
|
error = t('magiclink.apierror.xhrerror');
|
||||||
|
isLoading = false;
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex in-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
<div class="flex in-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
@ -10,8 +80,17 @@
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
<div>
|
<div>
|
||||||
<h1 class="font-semibold text-2xl">{t('magiclink.loadtitle')}</h1>
|
<h1 class="font-semibold text-2xl">{t('magiclink.loadtitle')}</h1>
|
||||||
<h2 class="ftext-sm">{t('magiclink.loadsubtitle')}</h2>
|
<h2 class="text-sm">{t('magiclink.loadsubtitle')}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
{#if hasError}
|
||||||
|
<h1 class="font-semibold text-2xl">{t('magiclink.failed')}</h1>
|
||||||
|
<h2 class="text-sm">{error}</h2>
|
||||||
|
<a class="text-xs font-bold text-purple-400 dark:text-purple-600" href="/auth/login">{t('magiclink.tryAgain')}</a>
|
||||||
|
{:else}
|
||||||
|
<h1 class="font-semibold text-2xl">{t('magiclink.finished')}</h1>
|
||||||
|
<h2 class="text-sm">{t('magiclink.redirecting')}</h2>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
Loading…
Reference in New Issue