Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api authentication with tokens #97

Merged
merged 23 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions assets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ async function deployUnikernel() {
formData.append("arguments", arguments)
formData.append("molly_csrf", molly_csrf)
try {
const response = await fetch("/unikernel/create", {
const response = await fetch("/api/unikernel/create", {
method: 'POST',
body: formData
})
Expand Down Expand Up @@ -219,7 +219,7 @@ async function deployUnikernel() {
async function restartUnikernel(name) {
try {
const molly_csrf = document.getElementById("molly-csrf").value;
const response = await fetch(`/unikernel/restart/${name}`, {
const response = await fetch(`/api/unikernel/restart/${name}`, {
method: 'POST',
body: JSON.stringify({ "name": name, "molly_csrf": molly_csrf }),
headers: { 'Content-Type': 'application/json' }
Expand All @@ -242,7 +242,7 @@ async function restartUnikernel(name) {
async function destroyUnikernel(name) {
try {
const molly_csrf = document.getElementById("molly-csrf").value;
const response = await fetch(`/unikernel/destroy/${name}`, {
const response = await fetch(`/api/unikernel/destroy/${name}`, {
method: 'POST',
body: JSON.stringify({ "name": name, "molly_csrf": molly_csrf }),
headers: { 'Content-Type': 'application/json' }
Expand Down Expand Up @@ -483,7 +483,7 @@ async function closeSessions() {
try {
buttonLoading(sessionButton, true, "Closing sessions..")
const molly_csrf = document.getElementById("molly-csrf").value;
const response = await fetch('/account/sessions/close', {
const response = await fetch('/api/account/sessions/close', {
method: 'POST',
body: JSON.stringify(
{
Expand All @@ -506,6 +506,35 @@ async function closeSessions() {
}
}

async function closeSession(session_value) {
const sessionButton = document.getElementById(`session-button-${session_value}`);
try {
buttonLoading(sessionButton, true, "Closing session..")
const molly_csrf = document.getElementById("molly-csrf").value;
const response = await fetch('/api/account/session/close', {
method: 'POST',
body: JSON.stringify(
{
session_value,
molly_csrf,
}),
headers: { 'Content-Type': 'application/json' }
});

const data = await response.json();
if (response.status === 200) {
postAlert("bg-primary-300", data.data);
setTimeout(() => window.location.reload(), 1000);
} else {
postAlert("bg-secondary-300", data.data);
buttonLoading(sessionButton, false, "Close session")
}
} catch (error) {
postAlert("bg-secondary-300", error);
buttonLoading(sessionButton, false, "Close session")
}
}

async function logout() {
const logoutButton = document.getElementById("logout-button");
try {
Expand Down
4 changes: 3 additions & 1 deletion error_page.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ let error_layout (error : Utils.Status.t) =
~a:[ a_class [ "text-5xl text-secondary-500 font-semibold" ] ]
[ txt (string_of_int error.code) ];
p ~a:[ a_class [ "uppercase font-bold text-5xl" ] ] [ txt error.title ];
p ~a:[ a_class [ "capitalize text-xl my-6" ] ] [ txt error.data ];
p
~a:[ a_class [ "text-xl my-6" ] ]
[ txt (Yojson.Basic.to_string error.data) ];
])
31 changes: 14 additions & 17 deletions middleware.ml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ let redirect_to_dashboard reqd ?(msg = "") () =
Httpaf.Reqd.respond_with_string reqd response msg;
Lwt.return_unit

let http_response ~title ?(header_list = []) ?(data = "") reqd http_status =
let http_response ~title ?(header_list = []) ?(data = `String "") reqd
hannesm marked this conversation as resolved.
Show resolved Hide resolved
http_status =
let code = Httpaf.Status.to_code http_status
and success = Httpaf.Status.is_successful http_status in
let status = { Utils.Status.code; title; data; success } in
Expand Down Expand Up @@ -138,23 +139,10 @@ let session_cookie_value reqd =
m "auth-middleware: No molly-session in cookie header.");
Error (`Msg "User not found")

let auth_middleware user handler reqd =
if user.User_model.active then handler reqd
else
redirect_to_page ~path:"/sign-in" ~clear_session:true ~with_error:true
~msg:"User account is deactivated." reqd ()

let email_verified_middleware user handler reqd =
if User_model.is_email_verified user then handler reqd
else redirect_to_verify_email reqd ()

let is_user_admin_middleware api_meth user handler reqd =
if user.User_model.super_user && user.active then handler reqd
else
redirect_to_error ~title:"Unauthorized"
~data:"You don't have the necessary permissions to access this service."
`Unauthorized 401 api_meth reqd ()

let csrf_cookie_verification form_csrf reqd =
match cookie User_model.csrf_cookie reqd with
| Some cookie -> (
Expand All @@ -173,8 +161,17 @@ let csrf_verification user now form_csrf handler reqd =
if User_model.is_valid_cookie csrf_token now then handler reqd
else
http_response ~title:"CSRF Token Mismatch"
~data:"Invalid CSRF token error. Please refresh and try again." reqd
`Bad_request
~data:
(`String "Invalid CSRF token error. Please refresh and try again.")
reqd `Bad_request
| None ->
http_response ~data:"Missing CSRF token. Please refresh and try again."
http_response
~data:(`String "Missing CSRF token. Please refresh and try again.")
~title:"Missing CSRF Token" reqd `Bad_request

let api_authentication reqd =
match header "Authorization" reqd with
| Some auth when String.starts_with ~prefix:"Bearer " auth ->
let token = String.sub auth 7 (String.length auth - 7) in
Some token
| _ -> None
44 changes: 44 additions & 0 deletions storage.ml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,50 @@ module Make (BLOCK : Mirage_block.S) = struct
| Some c -> Some (user, c)))
None store.users

let find_by_api_token store token =
List.find_map
(fun (user : User_model.user) ->
match
List.find_opt
(fun (token_ : User_model.token) -> String.equal token token_.value)
user.tokens
with
| Some token_ -> Some (user, token_)
| None -> None)
store.users

let increment_token_usage store (token : User_model.token)
(user : User_model.user) =
let token = { token with usage_count = token.usage_count + 1 } in
let tokens =
List.map
(fun (token' : User_model.token) ->
if String.equal token.value token'.value then token else token')
user.tokens
in
let updated_user = User_model.update_user user ~tokens () in
update_user store updated_user >>= function
| Ok () -> Lwt.return (Ok ())
| Error (`Msg err) ->
Logs.err (fun m -> m "Error with storage: %s" err);
Lwt.return (Error (`Msg err))

let update_cookie_usage store (cookie : User_model.cookie)
(user : User_model.user) reqd =
let cookie = { cookie with user_agent = Middleware.user_agent reqd } in
let cookies =
List.map
(fun (cookie' : User_model.cookie) ->
if String.equal cookie.value cookie'.value then cookie else cookie')
user.cookies
in
let updated_user = User_model.update_user user ~cookies () in
update_user store updated_user >>= function
| Ok () -> Lwt.return (Ok ())
| Error (`Msg err) ->
Logs.err (fun m -> m "Error with storage: %s" err);
Lwt.return (Error (`Msg err))

let count_users store = List.length store.users

let find_email_verification_token store uuid =
Expand Down
Loading
Loading