webui work
This commit is contained in:
parent
a9d6cb22db
commit
f4c0a1718e
|
@ -110,6 +110,32 @@ export async function authSession(email: string): Promise<[AuthResult, null | AP
|
|||
}
|
||||
}
|
||||
|
||||
export async function signup(email: string): Promise<[AuthResult, null | APIError]> {
|
||||
logger.info('sending signup');
|
||||
|
||||
try {
|
||||
logger.debug(`api call: baseurl ${PUBLIC_BASE_URL}`);
|
||||
const resp = await fetch(`${PUBLIC_BASE_URL}/v1/signup`, {
|
||||
'method': 'POST',
|
||||
'body': JSON.stringify({
|
||||
email: email
|
||||
}),
|
||||
'headers': {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const rawerror = JSON.parse(await resp.text()).errors[0];
|
||||
logger.error(`error sending authentication: ${rawerror.message}`);
|
||||
return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}];
|
||||
}
|
||||
return [AuthResult.Successful, null]
|
||||
} catch (e) {
|
||||
logger.error(`error making API request: ${e}`);
|
||||
return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}]
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyLink(ml: string): Promise<[AuthResult, null | APIError]> {
|
||||
logger.info('checking magic link authentication');
|
||||
|
||||
|
@ -164,10 +190,41 @@ export async function createTotp(token: string): Promise<[AuthResult, TOTPCreate
|
|||
logger.error(`error sending totp create: ${rawerror.message}`);
|
||||
return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}];
|
||||
}
|
||||
logger.info(`call success, ${await resp.text()}`)
|
||||
return [AuthResult.Successful, JSON.parse(await resp.text()).data]
|
||||
} catch (e) {
|
||||
logger.error(`error making API request: ${e}`);
|
||||
return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}]
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyTotp(token: string, totpToken: string, totpCode: string): Promise<[AuthResult, undefined | APIError]> {
|
||||
logger.info('verifying totp authenticator');
|
||||
|
||||
try {
|
||||
logger.debug(`api call: baseurl ${PUBLIC_BASE_URL}`);
|
||||
const resp = await fetch(`${PUBLIC_BASE_URL}/v1/totp-authenticators`, {
|
||||
'method': 'POST',
|
||||
'body': JSON.stringify({
|
||||
totpToken: totpToken,
|
||||
code: totpCode
|
||||
}),
|
||||
'headers': {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
if (!resp.ok) {
|
||||
logger.error('call returned error code');
|
||||
const rawerror = JSON.parse(await resp.text()).errors[0];
|
||||
logger.error(`error sending totp create: ${rawerror.message}`);
|
||||
return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}];
|
||||
}
|
||||
|
||||
window.localStorage.setItem("mfa", JSON.parse(await resp.text()).data.authToken);
|
||||
|
||||
return [AuthResult.Successful, undefined]
|
||||
} catch (e) {
|
||||
logger.error(`error making API request: ${e}`);
|
||||
return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
"itworks": {
|
||||
"header": "It works!",
|
||||
"body": "If you're seeing this page, tfweb is installed and (probably) correctly configured.",
|
||||
"linkbody": "Perhaps you meant to visit the {link0}?",
|
||||
"linkbody.link0": "admin panel"
|
||||
"linkbody": "Perhaps you meant to visit the {link0} or {link1}?",
|
||||
"linkbody.link0": "admin panel",
|
||||
"linkbody.link1": "create an account"
|
||||
},
|
||||
"login": {
|
||||
"title": "Log in to your account",
|
||||
|
@ -20,6 +21,22 @@
|
|||
"usermissing": "That user does not exist."
|
||||
}
|
||||
},
|
||||
"signup": {
|
||||
"title": "Create an account",
|
||||
"subtitle": "We'll send you an email with a \"magic link\"",
|
||||
"label": "What is your email?",
|
||||
"button": "Create account",
|
||||
"email": "Check your email",
|
||||
"emailbody": "We sent you an email with a link to complete signing up.",
|
||||
"emailbody2": "Didn't work? Check your junk inbox or click {link0} to try again.",
|
||||
"emailbody2.link0": "here",
|
||||
"error": {
|
||||
"invalidEmail": "That email address isn't valid. Try again.",
|
||||
"generic": "There was an error logging you in. Try again or contact support with the error code {err}",
|
||||
"userexists": "That user already exists. Try {link0}?",
|
||||
"userexists.link0": "logging in"
|
||||
}
|
||||
},
|
||||
"ml": {
|
||||
"header": "Authenticated!",
|
||||
"body": "Redirecting to admin page...",
|
||||
|
|
|
@ -21,5 +21,5 @@
|
|||
<h1>{$t('itworks.header')}</h1>
|
||||
<p>{$t('itworks.body')}</p>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<p>{@html $t('itworks.linkbody', {values:{link0:'<a href="/admin">'+$t('itworks.linkbody.link0')+'</a>'}})}</p>
|
||||
<p>{@html $t('itworks.linkbody', {values:{link0:'<a href="/admin">'+$t('itworks.linkbody.link0')+'</a>',link1:'<a href="/signup">'+$t('itworks.linkbody.link1')+'</a>'}})}</p>
|
||||
</LoadingWrapper>
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
import {isLoading, t} from "svelte-i18n";
|
||||
import LoadingWrapper from "$components/LoadingWrapper.svelte";
|
||||
import {onMount} from "svelte";
|
||||
import {AuthResult, authSession, createTotp, isAuthedSession} from "$lib/auth.ts";
|
||||
import {AuthResult, authSession, createTotp, isAuthedSession, verifyTotp} from "$lib/auth.ts";
|
||||
import type {TOTPCreateInfo} from "$lib/auth.ts";
|
||||
import type {SessionInfo} from "$lib/auth.ts";
|
||||
import type {APIError} from "$lib/auth.ts";
|
||||
import {Logger, logSetup} from "$lib/logger";
|
||||
|
@ -18,6 +19,10 @@
|
|||
let logger = new Logger("2fasetup/+page.svelte");
|
||||
|
||||
let totp_secret = '';
|
||||
let otp_url = '';
|
||||
let totp_create_token = '';
|
||||
|
||||
let code = '';
|
||||
|
||||
onMount(async () => {
|
||||
let session_load_info = await isAuthedSession();
|
||||
|
@ -54,10 +59,41 @@
|
|||
return;
|
||||
}
|
||||
|
||||
logger.info('setting secret and otpurl');
|
||||
|
||||
totp_secret = (create_res[1] as TOTPCreateInfo).secret;
|
||||
otp_url = (create_res[1] as TOTPCreateInfo).url;
|
||||
totp_create_token = (create_res[1] as TOTPCreateInfo).totpToken;
|
||||
|
||||
logger.info(totp_secret);
|
||||
logger.info(otp_url);
|
||||
|
||||
loading = false;
|
||||
})
|
||||
|
||||
async function onSubmit() {
|
||||
let create_res = await verifyTotp(window.localStorage.getItem("session"), totp_create_token, code);
|
||||
|
||||
if (create_res[0] == AuthResult.Failed) {
|
||||
logger.error(`totp auth fail`);
|
||||
isError = true;
|
||||
let err = create_res[1] as APIError;
|
||||
|
||||
let etext = err.code;
|
||||
|
||||
if (etext === "api_call_failed") {
|
||||
etext = $t('2fasetup.error.api');
|
||||
} else if (etext === "ERR_ALREADY_HAS_TOTP") {
|
||||
window.location.href = '/2fa';
|
||||
return;
|
||||
}
|
||||
|
||||
error = $t('2fasetup.error.generic', {values:{err:etext}});
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = '/admin';
|
||||
|
||||
}
|
||||
</script>
|
||||
|
@ -71,13 +107,13 @@
|
|||
<p>{$t('2fasetup.body')}</p>
|
||||
<h4>{$t('2fasetup.scan')}</h4>
|
||||
<div>
|
||||
<QrCode value="abcd1234"/>
|
||||
<QrCode bind:value={otp_url}/>
|
||||
</div>
|
||||
<h4>{$t('2fasetup.code')}</h4>
|
||||
<div><span>totp secret hete</span></div>
|
||||
<div><span>{totp_secret}</span></div>
|
||||
<form on:submit|preventDefault={onSubmit}>
|
||||
<label for="code">{$t('2fasetup.verify')}</label>
|
||||
<input type="number" min="0" max="999999" id="code" />
|
||||
<input bind:value={code} type="number" min="0" max="999999" id="code" />
|
||||
<button>{$t('2fasetup.button')}</button>
|
||||
</form>
|
||||
</LoadingWrapper>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import {isLoading, t} from "svelte-i18n";
|
||||
import LoadingWrapper from "$components/LoadingWrapper.svelte";
|
||||
import {onMount} from "svelte";
|
||||
import {AuthResult, isAuthedSession} from "$lib/auth.ts";
|
||||
import {AuthResult, isAuthedMFA, isAuthedSession} from "$lib/auth.ts";
|
||||
import {Logger, logSetup} from "$lib/logger";
|
||||
import type {APIError} from "$lib/auth.ts";
|
||||
|
||||
|
@ -23,6 +23,14 @@
|
|||
return;
|
||||
}
|
||||
|
||||
let mfa_load_info = await isAuthedMFA();
|
||||
if (mfa_load_info[0] == AuthResult.Failed) {
|
||||
let err = mfa_load_info[1] as APIError;
|
||||
logger.error(`mfa load failed: ${err.code} ${err.message}`);
|
||||
window.location.href = '/2fa';
|
||||
return;
|
||||
}
|
||||
|
||||
loading = false;
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
<script lang="ts">
|
||||
import {isLoading, t} from "svelte-i18n";
|
||||
import LoadingWrapper from "$components/LoadingWrapper.svelte";
|
||||
import {onMount} from "svelte";
|
||||
import {AuthResult, authSession, isAuthedSession, signup} from "$lib/auth.ts";
|
||||
import type {APIError} from "$lib/auth.ts";
|
||||
import {Logger, logSetup} from "$lib/logger";
|
||||
|
||||
let loading = true;
|
||||
let isError = false;
|
||||
let error = '';
|
||||
$: currentlyLoading = $isLoading || loading;
|
||||
|
||||
logSetup();
|
||||
let logger = new Logger("signup/+page.svelte");
|
||||
|
||||
onMount(async () => {
|
||||
let session_load_info = await isAuthedSession();
|
||||
if (session_load_info[0] != AuthResult.Failed) {
|
||||
logger.error(`session load success, the user is already logged in`);
|
||||
window.location.href = '/2fa';
|
||||
return;
|
||||
}
|
||||
|
||||
loading = false;
|
||||
})
|
||||
|
||||
let email = '';
|
||||
let email_regexp = new RegExp(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
|
||||
|
||||
let hasErrForm = false;
|
||||
let errForm = '';
|
||||
|
||||
let isDone = false;
|
||||
|
||||
async function onSubmit() {
|
||||
loading = true;
|
||||
|
||||
if (!email_regexp.test(email)) {
|
||||
errForm = $t('signup.error.invalidEmail');
|
||||
hasErrForm = true;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let auth_result = await signup(email);
|
||||
|
||||
if (auth_result[0] === AuthResult.Failed) {
|
||||
|
||||
hasErrForm = true;
|
||||
|
||||
// parse the error and filter out common ones for more specific error messages
|
||||
let err = auth_result[1] as APIError;
|
||||
|
||||
if (err.code == "ERR_USER_EXISTS") {
|
||||
errForm = $t('signup.error.userexists', {values:{link0:'<a href="/login">'+$t('signup.error.userexists.link0')+'</a>'}});
|
||||
} else {
|
||||
errForm = $t('signup.error.generic', {values: {err: (auth_result[1] as APIError).code}});
|
||||
}
|
||||
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isDone = true;
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t("common.title", {values: {title: $t("common.page.signup")}})}</title>
|
||||
</svelte:head>
|
||||
|
||||
<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}>
|
||||
{#if isDone}
|
||||
<h1>{$t('signup.email')}</h1>
|
||||
<p>{$t('signup.emailbody')}</p>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<p>{@html $t('signup.emailbody2', {values:{link0:'<a href="/signup">'+$t('signup.emailbody2.link0')+'</a>'}})}</p>
|
||||
{:else}
|
||||
<h1>{$t('signup.title')}</h1>
|
||||
<h3>{$t('signup.subtitle')}</h3>
|
||||
<form on:submit|preventDefault={onSubmit}>
|
||||
<label for="email">{$t('signup.label')}</label>
|
||||
<input type="email" bind:value={email} placeholder="john.doe@google.com" id="email"/>
|
||||
<button>{$t('signup.button')}</button>
|
||||
{#if hasErrForm}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<p>{@html errForm}</p>
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
</LoadingWrapper>
|
Loading…
Reference in New Issue