Skip to content

Commit

Permalink
Merge pull request #97 from robur-coop/api_auth
Browse files Browse the repository at this point in the history
Api authentication with tokens
  • Loading branch information
hannesm authored Dec 11, 2024
2 parents 4a85351 + 2d28fd3 commit f16ceb8
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 413 deletions.
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
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

0 comments on commit f16ceb8

Please sign in to comment.