diff --git a/tfweb/src/lib/auth.ts b/tfweb/src/lib/auth.ts new file mode 100644 index 0000000..bb8dd9e --- /dev/null +++ b/tfweb/src/lib/auth.ts @@ -0,0 +1,86 @@ +import {fetch_timeout} from "./util"; +import {t} from "./i18n"; +import {API_ROOT} from "./config"; +import {Logger, logSetup} from "./logger"; +import {getCookie} from "./cookie"; + +logSetup(); +const logger = new Logger("auth.ts"); + +export function redact_token(token: string) { + const stars = "*".repeat(token.length - 5); + return token.substring(5) + stars; +} + +export async function enforce_session(): Promise<[boolean, string]> { + logger.info("Checking session authentication"); + const session_token = getCookie("sessionToken"); + if (session_token === "") { + logger.error("No session token is present"); + return [false, ""]; + } + logger.info(`Session token is ${redact_token(session_token)}`); + + try { + const resp = await fetch_timeout(`${API_ROOT}/v1/auth/check_session`, { + 'method': 'POST', + 'headers': { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${session_token}` + } + }); + if (!resp.ok) { + const rawerror = JSON.parse(await resp.text()).errors[0].message; + return [false, rawerror]; + } else { + // session ok + return [true, session_token]; + } + } catch (e) { + // error in http request + return [false, `${e}`] + } + return [false, ""]; +} + +export async function enforce_auth(): Promise<[boolean, string]> { + logger.info("Checking mfa authentication"); + + const session_result = await enforce_session(); + + if (!session_result[0]) { + // session token is invalid + logger.error("Session token is invalid, therefore auth token cannot be valid"); + return [false, session_result[1]]; + } + + const session_token = session_result[1]; + + const auth_token = getCookie("authToken"); + if (auth_token === "") { + logger.error("No auth token is present"); + return [false, ""]; + } + logger.info(`MFA token is ${redact_token(auth_token)}`); + + try { + const resp = await fetch_timeout(`${API_ROOT}/v1/auth/check_auth`, { + 'method': 'POST', + 'headers': { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${session_token} ${auth_token}` + } + }); + if (!resp.ok) { + const rawerror = JSON.parse(await resp.text()).errors[0].message; + return [false, rawerror]; + } else { + // session ok + return [true, `${session_token} ${auth_token}`]; + } + } catch (e) { + // error in http request + return [false, `${e}`] + } + return [false, ""]; +} \ No newline at end of file diff --git a/tfweb/src/lib/cookie.ts b/tfweb/src/lib/cookie.ts index 01e3063..9fce1d5 100644 --- a/tfweb/src/lib/cookie.ts +++ b/tfweb/src/lib/cookie.ts @@ -5,7 +5,7 @@ export function setCookie(name: string, value: string, expires: number) { document.cookie = name + "=" + value + ";" + expires_at + ";path=/"; } -function getCookie(name: string): string { +export function getCookie(name: string): string { const name_with_equals = name + "="; const decodedCookie = decodeURIComponent(document.cookie); const ca = decodedCookie.split(';'); diff --git a/tfweb/src/lib/i18n/en.json b/tfweb/src/lib/i18n/en.json index c38769a..fbea936 100644 --- a/tfweb/src/lib/i18n/en.json +++ b/tfweb/src/lib/i18n/en.json @@ -20,7 +20,7 @@ "magicLinkExplainer": "We sent you a link, click on it to continue logging in.", "apierror": { "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" + "TypeError": "unable to contact server, please try again later" } }, diff --git a/tfweb/src/lib/util.ts b/tfweb/src/lib/util.ts new file mode 100644 index 0000000..2a17aba --- /dev/null +++ b/tfweb/src/lib/util.ts @@ -0,0 +1,14 @@ +export async function fetch_timeout(resource: RequestInfo | URL, options = {}) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const { timeout = 8000 } = options; + + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + const response = await fetch(resource, { + ...options, + signal: controller.signal + }); + clearTimeout(id); + return response; +} \ No newline at end of file diff --git a/tfweb/src/routes/admin/+page.svelte b/tfweb/src/routes/admin/+page.svelte new file mode 100644 index 0000000..bc9ccb7 --- /dev/null +++ b/tfweb/src/routes/admin/+page.svelte @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/tfweb/src/routes/auth/login/+page.svelte b/tfweb/src/routes/auth/login/+page.svelte index 10955d1..2e3f2d4 100644 --- a/tfweb/src/routes/auth/login/+page.svelte +++ b/tfweb/src/routes/auth/login/+page.svelte @@ -1,6 +1,8 @@ diff --git a/tfweb/src/routes/auth/magic-link/+page.svelte b/tfweb/src/routes/auth/magic-link/+page.svelte index 0ed49b2..ebefc29 100644 --- a/tfweb/src/routes/auth/magic-link/+page.svelte +++ b/tfweb/src/routes/auth/magic-link/+page.svelte @@ -64,6 +64,9 @@ isLoading = false; hasError = false; + + // redirect them to the homepage + window.location.href = "/admin" } }; diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 49bf9d0..6494d47 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -112,7 +112,11 @@ async fn main() -> Result<(), Box> { crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request, crate::routes::v1::verify_totp_authenticator::options, crate::routes::v1::auth::totp::totp_request, - crate::routes::v1::auth::totp::options + crate::routes::v1::auth::totp::options, + crate::routes::v1::auth::check_session::check_session, + crate::routes::v1::auth::check_session::check_session_auth, + crate::routes::v1::auth::check_session::options, + crate::routes::v1::auth::check_session::options_auth ]) .register("/", catchers![ crate::routes::handler_400, diff --git a/trifid-api/src/routes/v1/auth/check_session.rs b/trifid-api/src/routes/v1/auth/check_session.rs new file mode 100644 index 0000000..7d78d46 --- /dev/null +++ b/trifid-api/src/routes/v1/auth/check_session.rs @@ -0,0 +1,24 @@ +use rocket::{post, options}; +use crate::auth::{PartialUserInfo, TOTPAuthenticatedUserInfo}; + +#[options("/v1/auth/check_session")] +pub async fn options() -> &'static str { + "" +} + + +#[post("/v1/auth/check_session")] +pub async fn check_session(_user: PartialUserInfo) -> &'static str { + "ok" +} + +#[options("/v1/auth/check_auth")] +pub async fn options_auth() -> &'static str { + "" +} + + +#[post("/v1/auth/check_auth")] +pub async fn check_session_auth(_user: TOTPAuthenticatedUserInfo) -> &'static str { + "ok" +} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs index 7fce6ff..7a20be5 100644 --- a/trifid-api/src/routes/v1/auth/mod.rs +++ b/trifid-api/src/routes/v1/auth/mod.rs @@ -1,3 +1,4 @@ pub mod verify_magic_link; pub mod magic_link; -pub mod totp; \ No newline at end of file +pub mod totp; +pub mod check_session; \ No newline at end of file