[ui/api] check authentication on pages that need it / add endpoint to verify authentication

This commit is contained in:
c0repwn3r 2023-02-21 13:42:27 -05:00
parent f72cee6774
commit 33a0c077d1
Signed by: core
GPG Key ID: FDBF740DADDCEECF
10 changed files with 181 additions and 27 deletions

86
tfweb/src/lib/auth.ts Normal file
View File

@ -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, ""];
}

View File

@ -5,7 +5,7 @@ export function setCookie(name: string, value: string, expires: number) {
document.cookie = name + "=" + value + ";" + expires_at + ";path=/"; document.cookie = name + "=" + value + ";" + expires_at + ";path=/";
} }
function getCookie(name: string): string { export function getCookie(name: string): string {
const name_with_equals = name + "="; const name_with_equals = name + "=";
const decodedCookie = decodeURIComponent(document.cookie); const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(';'); const ca = decodedCookie.split(';');

View File

@ -20,7 +20,7 @@
"magicLinkExplainer": "We sent you a link, click on it to continue logging in.", "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" "TypeError": "unable to contact server, please try again later"
} }
}, },

14
tfweb/src/lib/util.ts Normal file
View File

@ -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;
}

View File

@ -0,0 +1,23 @@
<script lang="ts">
import {onMount} from "svelte";
import {enforce_auth, enforce_session} from "../../lib/auth";
// this page requires session and mfa auth.
onMount(() => {
let st_result = enforce_session();
if (!st_result[0]) {
// Session token is invalid. redirect to login
window.location = "/auth/login";
return;
}
let at_result = enforce_auth();
if (!at_result[0]) {
// Auth token is invalid. Redirect to mfa page.
window.location = "/auth/mfa";
return;
}
const api_token = at_result[1];
// user is fully authenticated and permitted to proceed
})
</script>

View File

@ -1,6 +1,8 @@
<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 {fetch_timeout} from "$lib/util";
import {Logger, logSetup} from "../../../lib/logger";
let email = ""; let email = "";
let isloading = false; let isloading = false;
@ -8,31 +10,29 @@
let hasError = false; let hasError = false;
let error = ""; let error = "";
function generateMagicLink() { logSetup();
let logger = new Logger("login/+page.svelte");
async function generateMagicLink() {
if (isloading) { if (isloading) {
return; return;
} }
isloading = true; isloading = true;
let xhr = new XMLHttpRequest();
xhr.timeout = 10000; try {
xhr.open('POST', `${API_ROOT}/v1/auth/magic-link`); let resp = await fetch_timeout(`${API_ROOT}/v1/auth/magic-link`, {
xhr.setRequestHeader("Content-Type", "application/json"); 'method': 'POST',
xhr.send(JSON.stringify({ 'headers': {
'Content-Type': 'application/json'
},
'body': JSON.stringify({
email: email email: email
})); })
});
xhr.ontimeout = () => { if (!resp.ok) {
hasError = true; hasError = true;
error = t('login.apierror.timeout'); const rawerror = JSON.parse(await resp.text()).errors[0].message;
isloading = false;
};
xhr.onload = () => {
if (xhr.status != 200) {
// error
hasError = true;
const rawerror = JSON.parse(xhr.responseText).errors[0].message;
error = t(`login.apierror.${rawerror}`); error = t(`login.apierror.${rawerror}`);
@ -41,13 +41,12 @@
isloading = false; isloading = false;
isFinished = true; isFinished = true;
} }
}; } catch (e) {
xhr.onerror = () => {
hasError = true; hasError = true;
error = t('login.apierror.xhrerror'); logger.error(`Error requesting magic link from api: ${e}`);
error = t(`login.apierror.${e.name}`);
isloading = false; isloading = false;
}; }
} }
</script> </script>

View File

@ -64,6 +64,9 @@
isLoading = false; isLoading = false;
hasError = false; hasError = false;
// redirect them to the homepage
window.location.href = "/admin"
} }
}; };

View File

@ -112,7 +112,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request, crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request,
crate::routes::v1::verify_totp_authenticator::options, crate::routes::v1::verify_totp_authenticator::options,
crate::routes::v1::auth::totp::totp_request, 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![ .register("/", catchers![
crate::routes::handler_400, crate::routes::handler_400,

View File

@ -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"
}

View File

@ -1,3 +1,4 @@
pub mod verify_magic_link; pub mod verify_magic_link;
pub mod magic_link; pub mod magic_link;
pub mod totp; pub mod totp;
pub mod check_session;