From f4c0a1718e6d6cf8fed15fe292e84bdee9eb2c1f Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sat, 10 Jun 2023 16:59:48 -0400 Subject: [PATCH] webui work --- tfweb/src/lib/auth.ts | 59 +++++++++++++++- tfweb/src/lib/i18n/locales/en.json | 21 +++++- tfweb/src/routes/+page.svelte | 2 +- tfweb/src/routes/2fasetup/+page.svelte | 44 ++++++++++-- tfweb/src/routes/admin/+page.svelte | 10 ++- tfweb/src/routes/signup/+page.svelte | 93 ++++++++++++++++++++++++++ 6 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 tfweb/src/routes/signup/+page.svelte diff --git a/tfweb/src/lib/auth.ts b/tfweb/src/lib/auth.ts index 7074283..ab9a690 100644 --- a/tfweb/src/lib/auth.ts +++ b/tfweb/src/lib/auth.ts @@ -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}`}] + } +} diff --git a/tfweb/src/lib/i18n/locales/en.json b/tfweb/src/lib/i18n/locales/en.json index 676fd42..6a19098 100644 --- a/tfweb/src/lib/i18n/locales/en.json +++ b/tfweb/src/lib/i18n/locales/en.json @@ -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...", diff --git a/tfweb/src/routes/+page.svelte b/tfweb/src/routes/+page.svelte index 2bf313c..27f53fb 100644 --- a/tfweb/src/routes/+page.svelte +++ b/tfweb/src/routes/+page.svelte @@ -21,5 +21,5 @@

{$t('itworks.header')}

{$t('itworks.body')}

-

{@html $t('itworks.linkbody', {values:{link0:''+$t('itworks.linkbody.link0')+''}})}

+

{@html $t('itworks.linkbody', {values:{link0:''+$t('itworks.linkbody.link0')+'',link1:''+$t('itworks.linkbody.link1')+''}})}

diff --git a/tfweb/src/routes/2fasetup/+page.svelte b/tfweb/src/routes/2fasetup/+page.svelte index 32d72cf..15dfa59 100644 --- a/tfweb/src/routes/2fasetup/+page.svelte +++ b/tfweb/src/routes/2fasetup/+page.svelte @@ -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'; } @@ -71,13 +107,13 @@

{$t('2fasetup.body')}

{$t('2fasetup.scan')}

- +

{$t('2fasetup.code')}

-
totp secret hete
+
{totp_secret}
- +
diff --git a/tfweb/src/routes/admin/+page.svelte b/tfweb/src/routes/admin/+page.svelte index 4d914cb..ac02147 100644 --- a/tfweb/src/routes/admin/+page.svelte +++ b/tfweb/src/routes/admin/+page.svelte @@ -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; }) diff --git a/tfweb/src/routes/signup/+page.svelte b/tfweb/src/routes/signup/+page.svelte new file mode 100644 index 0000000..32f52fb --- /dev/null +++ b/tfweb/src/routes/signup/+page.svelte @@ -0,0 +1,93 @@ + + + + {$t("common.title", {values: {title: $t("common.page.signup")}})} + + + + {#if isDone} +

{$t('signup.email')}

+

{$t('signup.emailbody')}

+ +

{@html $t('signup.emailbody2', {values:{link0:''+$t('signup.emailbody2.link0')+''}})}

+ {:else} +

{$t('signup.title')}

+

{$t('signup.subtitle')}

+
+ + + + {#if hasErrForm} + +

{@html errForm}

+ {/if} +
+ {/if} +