[ui/api] check authentication on pages that need it / add endpoint to verify authentication
This commit is contained in:
parent
f72cee6774
commit
33a0c077d1
|
@ -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, ""];
|
||||
}
|
|
@ -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(';');
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -1,6 +1,8 @@
|
|||
<script lang="ts">
|
||||
import {t} from "$lib/i18n";
|
||||
import {API_ROOT} from "$lib/config";
|
||||
import {fetch_timeout} from "$lib/util";
|
||||
import {Logger, logSetup} from "../../../lib/logger";
|
||||
|
||||
let email = "";
|
||||
let isloading = false;
|
||||
|
@ -8,31 +10,29 @@
|
|||
let hasError = false;
|
||||
let error = "";
|
||||
|
||||
function generateMagicLink() {
|
||||
logSetup();
|
||||
let logger = new Logger("login/+page.svelte");
|
||||
|
||||
async function generateMagicLink() {
|
||||
if (isloading) {
|
||||
return;
|
||||
}
|
||||
isloading = true;
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.timeout = 10000;
|
||||
xhr.open('POST', `${API_ROOT}/v1/auth/magic-link`);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({
|
||||
email: email
|
||||
}));
|
||||
|
||||
xhr.ontimeout = () => {
|
||||
hasError = true;
|
||||
error = t('login.apierror.timeout');
|
||||
isloading = false;
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status != 200) {
|
||||
// error
|
||||
try {
|
||||
let resp = await fetch_timeout(`${API_ROOT}/v1/auth/magic-link`, {
|
||||
'method': 'POST',
|
||||
'headers': {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
'body': JSON.stringify({
|
||||
email: email
|
||||
})
|
||||
});
|
||||
if (!resp.ok) {
|
||||
hasError = true;
|
||||
const rawerror = JSON.parse(xhr.responseText).errors[0].message;
|
||||
const rawerror = JSON.parse(await resp.text()).errors[0].message;
|
||||
|
||||
error = t(`login.apierror.${rawerror}`);
|
||||
|
||||
|
@ -41,13 +41,12 @@
|
|||
isloading = false;
|
||||
isFinished = true;
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
} catch (e) {
|
||||
hasError = true;
|
||||
error = t('login.apierror.xhrerror');
|
||||
logger.error(`Error requesting magic link from api: ${e}`);
|
||||
error = t(`login.apierror.${e.name}`);
|
||||
isloading = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -64,6 +64,9 @@
|
|||
|
||||
isLoading = false;
|
||||
hasError = false;
|
||||
|
||||
// redirect them to the homepage
|
||||
window.location.href = "/admin"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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::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,
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod verify_magic_link;
|
||||
pub mod magic_link;
|
||||
pub mod totp;
|
||||
pub mod totp;
|
||||
pub mod check_session;
|
Loading…
Reference in New Issue