aboutsummaryrefslogtreecommitdiff
path: root/captain
diff options
context:
space:
mode:
authorJeffrey Armstrong <jeff@approximatrix.com>2022-05-06 12:53:08 -0400
committerJeffrey Armstrong <jeff@approximatrix.com>2022-05-06 12:53:08 -0400
commit9917a9882eff675567007194661df57450016c89 (patch)
treee9d81ba6db3ee677a725180ed059cb0981384c34 /captain
parentf3b48d0678fe23c8ff4aed8bfdc370b3b8197b9f (diff)
downloadlevitating-9917a9882eff675567007194661df57450016c89.tar.gz
levitating-9917a9882eff675567007194661df57450016c89.zip
Login and session tracking now works via Gemini. Need to restrict operations in Gemini based on request auth levels still.
Diffstat (limited to 'captain')
-rw-r--r--captain/api.f902
-rw-r--r--captain/codes.f9023
-rw-r--r--captain/external.f90276
-rw-r--r--captain/gemini.f9039
-rw-r--r--captain/http.f906
-rw-r--r--captain/levitating-captain.prj3
-rw-r--r--captain/requtils.f9073
-rw-r--r--captain/response.f90149
-rw-r--r--captain/templates/index.gmi16
-rw-r--r--captain/web.f9031
10 files changed, 500 insertions, 118 deletions
diff --git a/captain/api.f90 b/captain/api.f90
index c05b0ba..f75a257 100644
--- a/captain/api.f90
+++ b/captain/api.f90
@@ -89,6 +89,7 @@ contains
use special_filenames
use logging
use security, only: validate_query_token
+ use gemini_codes
implicit none
type(request), intent(in)::req
@@ -202,6 +203,7 @@ contains
use special_filenames
use logging
use security, only: validate_token => validate_titan_token
+ use gemini_codes
implicit none
type(titan_request), intent(in)::req
diff --git a/captain/codes.f90 b/captain/codes.f90
new file mode 100644
index 0000000..be6c07e
--- /dev/null
+++ b/captain/codes.f90
@@ -0,0 +1,23 @@
+module http_codes
+implicit none
+
+ integer, parameter::HTTP_CODE_SUCCESS = 200
+ integer, parameter::HTTP_CODE_NOTFOUND = 404
+ integer, parameter::HTTP_CODE_FAILURE = 500
+ integer, parameter::HTTP_CODE_REDIRECT = 302
+ integer, parameter::HTTP_CODE_UNAUTHORIZED = 401
+
+end module http_codes
+
+module gemini_codes
+implicit none
+
+ integer, parameter::GEMINI_CODE_INPUT = 10
+ integer, parameter::GEMINI_CODE_INPUT_PW = 11
+ integer, parameter::GEMINI_CODE_SUCCESS = 20
+ integer, parameter::GEMINI_CODE_REDIRECT = 30
+ integer, parameter::GEMINI_CODE_TEMPFAIL = 40
+ integer, parameter::GEMINI_CODE_PERMFAIL = 50
+ integer, parameter::GEMINI_CODE_BAD_REQUEST = 59
+
+end module gemini_codes \ No newline at end of file
diff --git a/captain/external.f90 b/captain/external.f90
index 0cdcfdf..4c700ae 100644
--- a/captain/external.f90
+++ b/captain/external.f90
@@ -80,7 +80,8 @@ contains
use server_response
use special_filenames, only: get_task_result_static_filename
use request_utils, only: get_status_utf8, get_player_link => player_link, &
- get_instruction_link => instruction_link
+ get_instruction_link => instruction_link, &
+ build_link
implicit none
type(request), intent(in)::req
@@ -95,7 +96,7 @@ contains
type(task), dimension(:), pointer::tasks
character(32)::task_text, job_text
character(len=:), pointer::task_results_filename
- character(len=:), pointer::player_link, instruction_link
+ character(len=:), pointer::player_link, instruction_link, result_link
res => null()
@@ -135,8 +136,11 @@ contains
write(task_text, '(I8)') i
- res = trim(res)//nl//"=> /results/"//task_results_filename//" "// &
- trim(status)//" - Task "//trim(adjustl(task_text))
+ result_link => build_link("/results/"//task_results_filename, &
+ trim(status)//" - Task "//trim(adjustl(task_text)), &
+ .true., req%token)
+ res = trim(res)//nl//result_link
+ deallocate(result_link)
end do
deallocate(tasks)
@@ -158,6 +162,7 @@ contains
use utilities
use server_response
use config
+ use request_utils, only: build_link
implicit none
type(request), intent(in)::req
@@ -169,6 +174,7 @@ contains
integer::allocation_size, i
character(1)::nl = new_line(' ')
character(4)::folder_icon = char(240)//char(159)//char(147)//char(129)
+ character(len=:), pointer::release_link
if(.not. associated(req%query_string)) then
public_path = "/releases"
@@ -204,10 +210,15 @@ contains
if(trim(public_path) /= "/releases") then
i = index(req%query_string, "/", back=.true.)
if(i > 0) then
- res = trim(res)//nl//"=> /releases.gmi?"//req%query_string(1:(i-1))//" Up a directory"
+ release_link => build_link("/releases.gmi?"//req%query_string(1:(i-1)), &
+ " Up a directory", .true., req%token)
else
- res = trim(res)//nl//"=> /releases.gmi Up a directory"
+ release_link => build_link("/releases.gmi?"//req%query_string(1:(i-1)), &
+ " Up a directory", .true., req%token)
+
end if
+ res = trim(res)//nl//release_link
+ deallocate(release_link)
end if
if(associated(directories)) then
@@ -219,8 +230,13 @@ contains
else
subpath = trim(directories(i))
end if
+
+ release_link => build_link("/releases.gmi?"//trim(subpath), &
+ folder_icon//" "//trim(directories(i)), &
+ .true., req%token)
- res = trim(res)//nl//"=> /releases.gmi?"//trim(subpath)//" "//folder_icon//" "//trim(directories(i))
+ res = trim(res)//nl//release_link
+ deallocate(release_link)
end do
deallocate(directories)
@@ -231,7 +247,9 @@ contains
do i = 1, size(files)
call combine_paths(public_path, trim(files(i)), subpath)
- res = trim(res)//nl//"=> "//trim(subpath)//" "//trim(files(i))
+ release_link => build_link(trim(subpath), trim(files(i)), .true., req%token)
+ res = trim(res)//nl//release_link
+ deallocate(release_link)
end do
deallocate(files)
@@ -240,17 +258,19 @@ contains
end function generate_releases_gemini
- function generate_players_gemini() result(res)
+ function generate_players_gemini(req) result(res)
use captain_db
- use request_utils, only: get_player_status_utf8
+ use request_utils, only: get_player_status_utf8, build_link
+ use server_response, only: request
implicit none
+ type(request), intent(in)::req
character(len=:), pointer::res
character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::players
character(4)::player_status
integer::n, i, nsize
- character(len=3*PLAYER_NAME_LENGTH)::one_player
+ character(len=:), pointer::player_link
n = get_player_count()
if(n == 0) then
@@ -274,27 +294,34 @@ contains
do i = 1, n
player_status = get_player_status_utf8(players(i))
- one_player = "=> /players/"//trim(players(i))//".gmi "//trim(player_status)//" "//trim(players(i))
+ player_link => build_link("/players/"//trim(players(i))//".gmi", &
+ trim(player_status)//" "//trim(players(i)), &
+ .true., req%token)
+
if(i == 1) then
- res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//trim(one_player)
+ res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//player_link
else
- res = trim(res)//new_line(res(1:1))//trim(one_player)
+ res = trim(res)//new_line(res(1:1))//player_link
end if
+ deallocate(player_link)
end do
deallocate(players)
end if
+ player_link => build_link("/players/add.gmi", "Add Player", .true., req%token)
+
res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//"## Management"// &
- new_line(res(1:1))//"=> /players/add.gmi Add Player"
+ new_line(res(1:1))//player_link
+
+ deallocate(player_link)
end function generate_players_gemini
function generate_one_instuction_gemini(req) result(res)
use captain_db
use server_response
- use request_utils, only: get_player_status_utf8
- use request_utils, only: render_jobs_links
+ use request_utils, only: get_player_status_utf8, render_jobs_links, build_link
implicit none
type(request)::req
@@ -307,7 +334,7 @@ contains
character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::all_players
integer::i, j, n_jobs, n_players, nsize
- character(len=:), pointer::job_link_text
+ character(len=:), pointer::job_link_text, raw_link, launch_link, assign_link
character(1)::nl = new_line(' ')
character(PLAYER_NAME_LENGTH)::player_name
character(4)::player_status
@@ -348,7 +375,10 @@ contains
res = nl//"## "//trim(instruction_name)
i = index(req%location, ".gmi", back=.true.)
- res = trim(res)//nl//"=> "//req%location(1:i-1)//".json View Raw"
+
+ raw_link => build_link(req%location(1:i-1)//".json", "View Raw", .true., req%token)
+ res = trim(res)//nl//raw_link
+ deallocate(raw_link)
if(n_players == 0) then
res = trim(res)//nl//nl//"No players currently can run these instructions"
@@ -357,8 +387,13 @@ contains
do i = 1, n_players
call get_player_name(players(i), player_name)
player_status = get_player_status_utf8(players(i))
- res = trim(res)//nl//"=> "//trim(req%location)//"?launch="//trim(player_name)// &
- " "//trim(player_status)//" "//trim(player_name)
+
+ launch_link => build_link(trim(req%location)//"?launch="//trim(player_name), &
+ trim(player_status)//" "//trim(player_name), &
+ .true., req%token)
+
+ res = trim(res)//nl//launch_link
+ deallocate(launch_link)
end do
end if
@@ -380,8 +415,13 @@ contains
cycle
end if
end if
- res = trim(res)//nl//"=> "//trim(req%location)//"?assign="//trim(all_players(i))// &
- " "//trim(all_players(i))
+
+ assign_link => build_link(trim(req%location)//"?assign="//trim(all_players(i)), &
+ trim(all_players(i)), .true., req%token)
+
+ res = trim(res)//nl//assign_link
+
+ deallocate(assign_link)
end do
deallocate(all_players)
end if
@@ -390,22 +430,31 @@ contains
res = trim(res)//nl//nl//"### Remove"//nl//"Remove a player from these instructions"
do i = 1, n_players
call get_player_name(players(i), player_name)
- res = trim(res)//nl//"=> "//trim(req%location)//"?remove="//trim(player_name)// &
- " "//trim(player_name)
+
+ assign_link => build_link(trim(req%location)//"?remove="//trim(player_name), &
+ trim(player_name), .true., req%token)
+
+ res = trim(res)//nl//assign_link
+
+ deallocate(assign_link)
end do
end if
end function generate_one_instuction_gemini
- function generate_instructions_gemini() result(res)
+ function generate_instructions_gemini(req) result(res)
use captain_db
+ use server_response, only: request
+ use request_utils, only: build_link
implicit none
+ class(request), intent(in)::req
character(len=:), pointer::res
character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::instruction_names
integer::n, i, nsize
character(len=3*PLAYER_NAME_LENGTH)::one_player
+ character(len=:), pointer::instruction_link
n = get_instructions_count()
@@ -426,23 +475,73 @@ contains
res = "## Instructions"
do i = 1, n
- one_player = "=> /instructions/"//trim(instruction_names(i))//".gmi "//trim(instruction_names(i))
+
+ instruction_link => build_link("/instructions/"//trim(instruction_names(i))//".gmi", &
+ trim(instruction_names(i)), .true., req%token)
+
if(i == 1) then
- res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//trim(one_player)
+ res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//instruction_link
else
- res = trim(res)//new_line(res(1:1))//trim(one_player)
+ res = trim(res)//new_line(res(1:1))//instruction_link
end if
+
+ deallocate(instruction_link)
end do
deallocate(instruction_names)
end if
+ instruction_link => build_link("/instructions/scan.gmi", "Scan for Instructions", &
+ .true., req%token)
+
res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//"## Management"// &
- new_line(res(1:1))//"=> /instructions/scan.gmi Scan for Instructions"
+ new_line(res(1:1))//instruction_link
+
+ deallocate(instruction_link)
end function generate_instructions_gemini
+ function generate_profile_gemini(req) result(res)
+ use server_response, only: request
+ use request_utils, only: build_link
+ use captain_db
+ implicit none
+
+ class(request), intent(in)::req
+
+ character(len=:), pointer::res
+ character(len=:), pointer::link
+ character(len=128)::username
+
+ if(.not. associated(req%token)) then
+
+ allocate(character(len=128)::res)
+ res = "Not currently logged in."//new_line(' ')//"=> /login.gmi Login"
+ return
+
+ else if(.not. is_valid_session_db(req%token)) then
+
+ call destroy_session_db(req%token)
+
+ allocate(character(len=128)::res)
+ res = "You need to login again."//new_line(' ')//"=> /login.gmi Login"
+ return
+
+ end if
+
+ allocate(character(len=1024)::res)
+ call get_session_username_db(req%token, username)
+ res = "## "//trim(username)//" Profile"
+
+ link => build_link("/logout.gmi", "Logout", .true., req%token)
+ res = trim(res)//new_line(' ')//link
+ deallocate(link)
+
+ res = trim(res)//new_line(' ')//new_line(' ')//"More to come soon!"
+
+ end function generate_profile_gemini
+
pure function is_input_provided_request(req)
use server_response, only: request
implicit none
@@ -454,15 +553,21 @@ contains
end function is_input_provided_request
- pure function is_input_required_request(req)
+ function is_input_required_request(req)
use server_response, only: request
implicit none
class(request), intent(in)::req
logical::is_input_required_request
+ character(64)::first
+
+ call req%path_component(1, first)
is_input_required_request = .false.
- if(req%location == "/players/add.gmi") then
+ if(req%location == "/players/add.gmi" .or. &
+ req%location == "/login.gmi" .or. &
+ trim(first) == "login") &
+ then
is_input_required_request = .true.
end if
@@ -470,15 +575,25 @@ contains
function external_input_required_gemini(req) result(resp)
use server_response
+ use gemini_codes
implicit none
class(request), intent(in)::req
type(response)::resp
+ character(64)::first
+
+ call req%path_component(1, first)
+
resp%code = GEMINI_CODE_INPUT
if(req%location == "/players/add.gmi") then
call resp%set_message("Enter name of new player to add")
+ else if(req%location == "/login.gmi") then
+ call resp%set_message("Enter username:")
+ else if(trim(first) == "login") then
+ call resp%set_message("Enter password:")
+ resp%code = GEMINI_CODE_INPUT_PW
end if
end function external_input_required_gemini
@@ -487,18 +602,22 @@ contains
use server_response
use captain_db
use request_utils, only: handle_instruction_command
+ use m_uuid, only: UUID_LENGTH
+ use logging
+ use gemini_codes
implicit none
class(request), intent(in)::req
type(response)::resp
- character(64)::first
+ character(64)::first, second
+ character(len=:), pointer::session
call req%path_component(1, first)
if(req%location == "/players/add.gmi") then
call add_player_db(req%query_string)
resp%code = GEMINI_CODE_REDIRECT
- call resp%set_url("/players.gmi")
+ call resp%set_gemini_session_url("/players.gmi", req%token)
else if(req%location == "/jobs.gmi" .or. req%location == "/releases.gmi") then
! Used for paging (jobs) or subdirs (releases) - send it back
@@ -511,7 +630,28 @@ contains
! Go back to the same location
resp%code = GEMINI_CODE_REDIRECT
call resp%set_url(req%location)
+
+ else if(req%location == "/login.gmi" .and. req%has_query()) then
+
+ resp%code = GEMINI_CODE_REDIRECT
+ call resp%set_url("/login/"//req%query_string//"/password.gmi")
+
+ else if(trim(first) == "login") then
+ call req%path_component(2, second)
+ resp%code = GEMINI_CODE_REDIRECT
+
+ call write_log("Attempting to validate "//trim(second)//":"//trim(req%query_string), LOG_DEBUG)
+
+ if(validate_user_db(trim(second), trim(req%query_string))) then
+ allocate(character(len=UUID_LENGTH)::session)
+ session = create_user_session_db(trim(second))
+ call resp%set_gemini_session_url("/index.gmi", session)
+ deallocate(session)
+ else
+ call resp%set_url("/loginfailed.gmi")
+ end if
+
end if
end function external_input_request_gemini
@@ -524,19 +664,52 @@ contains
logical::is_redirect_action
is_redirect_action = .false.
- if(req%location == "/instructions/scan.gmi") then
+ if(req%location == "instructions/scan.gmi" .or. &
+ req%location == "/logout.gmi") &
+ then
is_redirect_action = .true.
end if
end function is_redirect_action
+ subroutine handle_basic_gemini_template_components(req, page)
+ use server_response
+ use page_template
+ use captain_db
+ use config
+ implicit none
+
+ type(request), intent(in)::req
+ type(template), intent(inout)::page
+ character(len=128)::username
+
+ call page%assign('project', project)
+
+ if(req%is_authenticated_user()) then
+
+ call page%assign("session_flag", "/session-"//req%token)
+
+ call page%assign('user_link_page', "/profile.gmi")
+ call get_session_username_db(req%token, username)
+ call page%assign('user_link_text', achar(240)//achar(159)//achar(152)//achar(128)//" "//trim(username))
+
+ else
+ call page%assign("session_flag", "")
+ call page%assign('user_link_page', "/login.gmi")
+ call page%assign('user_link_text', "Login")
+
+ end if
+
+ end subroutine handle_basic_gemini_template_components
+
function external_redirect_action_request_gemini(req) result(resp)
use captain_db
use server_response
use logging
+ use gemini_codes
implicit none
- class(request), intent(in)::req
+ class(request), intent(inout)::req
type(response)::resp
resp%code = GEMINI_CODE_REDIRECT
@@ -544,6 +717,15 @@ contains
if(req%location == "/instructions/scan.gmi") then
call scan_instructions_for_db()
call resp%set_url("/instructions.gmi")
+
+ else if(req%location == "/logout.gmi") then
+
+ if(associated(req%token)) then
+ call destroy_session_db(req%token)
+ call req%clear_token()
+ end if
+ call resp%set_url("/index.gmi")
+
end if
end function external_redirect_action_request_gemini
@@ -555,6 +737,7 @@ contains
use server_response
use request_utils, only: get_job_page_title
use utilities, only: build_date
+ use gemini_codes
implicit none
class(request), intent(in)::req
@@ -599,7 +782,7 @@ contains
else if(trim(req%location) == "/players.gmi") then
call page%assign('title', 'Players')
- contents => generate_players_gemini()
+ contents => generate_players_gemini(req)
call page%assign('contents', contents)
else if(req%location(1:9) == '/players/') then
@@ -612,7 +795,7 @@ contains
else if(trim(req%location) == "/instructions.gmi") then
call page%assign('title', 'Build Instructions')
- contents => generate_instructions_gemini()
+ contents => generate_instructions_gemini(req)
call page%assign('contents', contents)
else if(trim(first) == "instructions") then
@@ -628,14 +811,24 @@ contains
contents => generate_one_job_gemini(req)
call page%assign('contents', contents)
+ else if(req%location == "/profile.gmi") then
+
+ call page%assign('title', "User Profile")
+ contents => generate_profile_gemini(req)
+ call page%assign('contents', contents)
+
+ else if(req%location == "/loginfailed.gmi") then
+
+ call page%assign('title', "Login Failed")
+ call page%assign("contents", "Bad username and/or password.")
+
else
call page%assign('title', 'Not Found')
end if
- call page%assign('project', project)
-
+ call handle_basic_gemini_template_components(req, page)
call write_log("Rendering page for "//req%location)
call page%render()
@@ -654,7 +847,7 @@ contains
use request_utils, only: is_request_static, request_static
implicit none
- class(request), intent(in)::req
+ class(request), intent(inout)::req ! inout for logout actions, unfortunately...
type(response)::resp
if(is_redirect_action(req)) then
@@ -687,6 +880,7 @@ contains
use query_utilities
use security
use logging
+ use gemini_codes
implicit none
type(titan_request), intent(in)::req
diff --git a/captain/gemini.f90 b/captain/gemini.f90
index 6070dbe..7e849d6 100644
--- a/captain/gemini.f90
+++ b/captain/gemini.f90
@@ -26,7 +26,7 @@ implicit none
private
public :: handle_request
-
+
contains
subroutine read_request(ssl, req)
@@ -127,7 +127,7 @@ contains
use iso_c_binding, only: c_ptr
use jessl, only: ssl_write
use logging
- use server_response, only: GEMINI_CODE_SUCCESS
+ use gemini_codes
implicit none
type(c_ptr)::ssl
@@ -148,7 +148,7 @@ contains
subroutine write_redirect(ssl, url)
use iso_c_binding, only: c_ptr
- use server_response, only: GEMINI_CODE_REDIRECT
+ use gemini_codes
implicit none
type(c_ptr)::ssl
@@ -158,21 +158,26 @@ contains
end subroutine write_redirect
- subroutine write_input_request(ssl, msg)
+ subroutine write_input_request(ssl, msg, code)
use iso_c_binding, only: c_ptr
- use server_response, only: GEMINI_CODE_INPUT
+ use gemini_codes
implicit none
type(c_ptr)::ssl
character(*), intent(in)::msg
+ integer, intent(in), optional::code
- call write_status(ssl, GEMINI_CODE_INPUT, msg)
+ if(present(code)) then
+ call write_status(ssl, code, msg)
+ else
+ call write_status(ssl, GEMINI_CODE_INPUT, msg)
+ end if
end subroutine write_input_request
subroutine write_failure(ssl)
use iso_c_binding, only: c_ptr
- use server_response, only: GEMINI_CODE_PERMFAIL
+ use gemini_codes
implicit none
type(c_ptr)::ssl
@@ -211,7 +216,9 @@ contains
use external_handling, only: external_request_gemini, external_request_titan
use api_handling
use logging
+ use m_uuid, only: UUID_LENGTH
use server_response
+ use gemini_codes
implicit none
! For our TLS connection
@@ -227,7 +234,7 @@ contains
! Requested file
character(1024)::text_request
- character(32)::first
+ character(UUID_LENGTH+8)::first
integer::rendered_unit, ioerror
@@ -307,6 +314,18 @@ contains
end if
else
+
+ ! Check for leading session
+ if(first(1:8) == "session-") then
+
+ ! Set the token in the request to the leading session id
+ call req%set_token(first(9:len(first)))
+
+ ! Remove the first path component
+ call req%remove_first_path_component()
+
+ end if
+
if(req%protocol == "gemini") then
resp = external_request_gemini(req)
@@ -320,8 +339,8 @@ contains
call write_log("Handling response", LOG_DEBUG)
! Handle the response
select case(resp%code)
- case(GEMINI_CODE_INPUT)
- call write_input_request(ssl, resp%message)
+ case(GEMINI_CODE_INPUT, GEMINI_CODE_INPUT_PW)
+ call write_input_request(ssl, resp%message, code=resp%code)
case(GEMINI_CODE_REDIRECT)
call write_redirect(ssl, resp%url)
diff --git a/captain/http.f90 b/captain/http.f90
index 8449ba2..61715fd 100644
--- a/captain/http.f90
+++ b/captain/http.f90
@@ -23,12 +23,6 @@
module http
implicit none
- integer, parameter::HTTP_CODE_SUCCESS = 200
- integer, parameter::HTTP_CODE_NOTFOUND = 404
- integer, parameter::HTTP_CODE_FAILURE = 500
- integer, parameter::HTTP_CODE_REDIRECT = 302
- integer, parameter::HTTP_CODE_UNAUTHORIZED = 401
-
contains
subroutine write_status(outunit, code)
diff --git a/captain/levitating-captain.prj b/captain/levitating-captain.prj
index f949fb2..078cccb 100644
--- a/captain/levitating-captain.prj
+++ b/captain/levitating-captain.prj
@@ -99,6 +99,9 @@
"filename":"captain.f90",
"enabled":"1"
},{
+ "filename":"codes.f90",
+ "enabled":"1"
+ },{
"filename":"config.f90",
"enabled":"1"
},{
diff --git a/captain/requtils.f90 b/captain/requtils.f90
index 620170a..03fe869 100644
--- a/captain/requtils.f90
+++ b/captain/requtils.f90
@@ -41,8 +41,9 @@ implicit none
contains
pure function success_code(req)
- use http, only: HTTP_SUCCESS => HTTP_CODE_SUCCESS
- use server_response, only: request, GEMINI_SUCCESS => GEMINI_CODE_SUCCESS
+ use http_codes, only: HTTP_SUCCESS => HTTP_CODE_SUCCESS
+ use gemini_codes, only: GEMINI_SUCCESS => GEMINI_CODE_SUCCESS
+ use server_response, only: request
implicit none
class(request), intent(in)::req
@@ -57,8 +58,9 @@ contains
end function success_code
pure function notfound_code(req)
- use http, only: HTTP_FAIL => HTTP_CODE_NOTFOUND
- use server_response, only: request, GEMINI_FAIL => GEMINI_CODE_PERMFAIL
+ use http_codes, only: HTTP_FAIL => HTTP_CODE_NOTFOUND
+ use gemini_codes, only: GEMINI_FAIL => GEMINI_CODE_PERMFAIL
+ use server_response, only: request
implicit none
class(request), intent(in)::req
@@ -73,8 +75,9 @@ contains
end function notfound_code
pure function notpermitted_code(req)
- use http, only: HTTP_UNAUTHORIZED => HTTP_CODE_UNAUTHORIZED
- use server_response, only: request, GEMINI_UNAUTHORIZED => GEMINI_CODE_BAD_REQUEST
+ use http_codes, only: HTTP_UNAUTHORIZED => HTTP_CODE_UNAUTHORIZED
+ use gemini_codes, only: GEMINI_UNAUTHORIZED => GEMINI_CODE_BAD_REQUEST
+ use server_response, only: request
implicit none
class(request), intent(in)::req
@@ -285,19 +288,31 @@ contains
end function request_static
- function build_link(link, label, gemini_mode) result(res)
+ function build_link(link, label, gemini_mode, session_token) result(res)
+ use server_response, only: gemini_session_link_url
implicit none
character(*), intent(in)::link, label
logical, intent(in)::gemini_mode
+
+ ! Passed in to prepend a session identifier in gemini
+ character(len=:), pointer, intent(in), optional::session_token
character(len=:), pointer::res
+ character(len=:), pointer::gemlink
integer::nl
if(gemini_mode) then
- nl = len_trim(link) + len_trim(label) + len('=> ')
+ if(present(session_token)) then
+ gemlink => gemini_session_link_url(link, session_token)
+ else
+ gemlink => gemini_session_link_url(link, null())
+ end if
+
+ nl = len_trim(gemlink) + len_trim(label) + len('=> ')
+
allocate(character(len=nl)::res)
- res = '=> '//trim(link)//' '//trim(label)
+ res = '=> '//trim(gemlink)//' '//trim(label)
else
@@ -530,7 +545,7 @@ contains
end function generate_simple_pager
- function render_jobs_links(jobs, startindex, stopindex, gemini_mode, link_prefix) result(res)
+ function render_jobs_links(jobs, startindex, stopindex, gemini_mode, link_prefix, session_token) result(res)
use captain_db
implicit none
@@ -538,11 +553,16 @@ contains
integer, intent(in), optional::startindex, stopindex
logical, intent(in)::gemini_mode
character(*), intent(in), optional::link_prefix
+ character(len=:), intent(in), pointer, optional::session_token
character(len=:), pointer::res
integer::nsize, i, first, last
character(len=16)::int_text
character(len=(2*PLAYER_NAME_LENGTH + 64))::link
+
+ character(len=:), pointer::link_pointer
+ character(len=:), pointer::internal_session_token
+
character(PLAYER_NAME_LENGTH)::player, instruction
character(1)::nl = new_line(' ')
character(64)::int_link_prefix
@@ -553,6 +573,12 @@ contains
int_link_prefix = " "
end if
+ if(present(session_token)) then
+ internal_session_token => session_token
+ else
+ internal_session_token => null()
+ end if
+
if(.not. associated(jobs)) then
allocate(character(len=32)::res)
res = "None Yet"
@@ -585,13 +611,17 @@ contains
write(int_text, '(I8)') jobs(i)%id
if(gemini_mode) then
- link = "=> "//trim(int_link_prefix)//"jobs/"// &
- trim(adjustl(int_text))//".gmi "// &
- trim(get_status_utf8(jobs(i)%status))//" Job "// &
- trim(adjustl(int_text))//" - "//trim(instruction)
+
+ link_pointer => build_link(trim(int_link_prefix)//"jobs/"//trim(adjustl(int_text))//".gmi", &
+ trim(get_status_utf8(jobs(i)%status))//" Job "// &
+ trim(adjustl(int_text))//" - "//trim(instruction), &
+ .true., internal_session_token)
- res = trim(res)//nl//nl//trim(link)
+ res = trim(res)//nl//nl//link_pointer
+ deallocate(link_pointer)
+
else
+
res = trim(res)//nl//' <li><div class="job_result_listitem'
select case(jobs(i)%status)
@@ -607,14 +637,13 @@ contains
res = trim(res)//'">'
end select
-
- link = ' <p><strong><a href="'//trim(int_link_prefix)//'jobs/'// &
- trim(adjustl(int_text))//'.html" >'// &
- trim(get_status_utf8(jobs(i)%status))//" Job "// &
- trim(adjustl(int_text))//" - "//trim(instruction)// &
- '</a></strong></p>'
+ link_pointer => build_link(trim(int_link_prefix)//'jobs/'//trim(adjustl(int_text))//'.html', &
+ trim(get_status_utf8(jobs(i)%status))//" Job "// &
+ trim(adjustl(int_text))//" - "//trim(instruction), &
+ .false., internal_session_token)
- res = trim(res)//nl//trim(link)//nl//" <p><em>"
+ res = trim(res)//nl//"<p><strong>"//link_pointer//'</strong></p>'//nl//"<p><em>"
+ deallocate(link_pointer)
endif
select case(jobs(i)%status)
diff --git a/captain/response.f90 b/captain/response.f90
index e172cc1..03d420e 100644
--- a/captain/response.f90
+++ b/captain/response.f90
@@ -25,13 +25,6 @@ use iso_c_binding
use query_utilities
implicit none
- integer, parameter::GEMINI_CODE_INPUT = 10
- integer, parameter::GEMINI_CODE_SUCCESS = 20
- integer, parameter::GEMINI_CODE_REDIRECT = 30
- integer, parameter::GEMINI_CODE_TEMPFAIL = 40
- integer, parameter::GEMINI_CODE_PERMFAIL = 50
- integer, parameter::GEMINI_CODE_BAD_REQUEST = 59
-
character(*), parameter::RESPONSE_JSON_OKAY = '{"status": "okay"}'
type :: response
@@ -55,6 +48,7 @@ implicit none
procedure :: destroy => response_destroy
procedure :: set_message => response_set_message
procedure :: set_url => response_set_url
+ procedure :: set_gemini_session_url => reponse_set_gemini_session_url
procedure :: set_body_contents => response_temp_file_contents
procedure :: set_filename => response_set_filename_using_allocation
procedure :: set_cookie_cmd => response_set_cookie_cmd
@@ -90,6 +84,10 @@ implicit none
procedure :: is_get => request_is_get
procedure :: is_post => request_is_post
procedure :: has_query => request_has_query
+ procedure :: is_authenticated_user => request_is_authenticated_user
+ procedure :: set_token => request_set_token
+ procedure :: remove_first_path_component => request_remove_first_path_component
+ procedure :: clear_token => request_clear_token
end type request
@@ -110,6 +108,37 @@ implicit none
contains
+ function gemini_session_link_url(link, session_token) result(res)
+ implicit none
+
+ character(*), intent(in)::link
+ character(len=:), pointer, intent(in), optional::session_token
+ character(len=:), pointer::res
+ logical::prepend_session
+ integer::nl
+
+ nl = len_trim(link)
+
+ prepend_session = .false.
+ if(link(1:1) == "/") then
+ if(present(session_token)) then
+ if(associated(session_token)) then
+ nl = nl + 9 + len_trim(session_token)
+ prepend_session = .true.
+ end if
+ end if
+ end if
+
+ allocate(character(len=nl)::res)
+
+ if(prepend_session) then
+ res = "/session-"//trim(session_token)//trim(link)
+ else
+ res = link
+ end if
+
+ end function gemini_session_link_url
+
subroutine request_init(self, str, server_explicit, protocol_explicit, method, cookiestring)
use logging
use utilities, only: toupper
@@ -269,6 +298,23 @@ contains
end function request_component_start_location
+ function request_is_authenticated_user(self)
+ use captain_db
+ implicit none
+
+ class(request), intent(in)::self
+ logical::request_is_authenticated_user
+
+ request_is_authenticated_user = .false.
+ if(associated(self%token)) then
+ request_is_authenticated_user = is_valid_session_db(self%token)
+ if(request_is_authenticated_user) then
+ call update_session_db(self%token)
+ end if
+ end if
+
+ end function request_is_authenticated_user
+
subroutine request_component(self, i_component, res)
use logging
implicit none
@@ -414,6 +460,73 @@ contains
end function request_has_query
+ subroutine request_set_token(self, token)
+ implicit none
+
+ class(request), intent(inout)::self
+ character(len=*), intent(in)::token
+
+ allocate(character(len=len_trim(token)) :: self%token)
+ self%token = token
+
+ end subroutine request_set_token
+
+ subroutine request_clear_token(self)
+ implicit none
+
+ class(request), intent(inout)::self
+ logical::safe_to_deallocate
+
+ if(associated(self%token)) then
+ safe_to_deallocate = .not. (associated(self%c%get_value("token"), self%token) .or. &
+ associated(self%q%get_value("token"), self%token))
+
+ if(safe_to_deallocate) then
+ deallocate(self%token)
+ end if
+
+ self%token => null()
+
+ end if
+
+ end subroutine request_clear_token
+
+ ! This routine is needed if we're stripping session identifiers
+ ! in Gemini URLs
+ subroutine request_remove_first_path_component(self)
+ implicit none
+
+ class(request), intent(inout)::self
+ character(len=:), pointer::first, newloc
+
+ integer::i, j, n
+
+ i = 1
+ if(self%location(1:1) == "/") then
+ i = i + 1
+ end if
+
+ n = len_trim(self%location)
+ j = index(self%location(i:n), "/") + (i-1)
+ if(j == 0) then
+ j = n
+ end if
+
+ ! First component should be self%location(i:j)
+ n = n - (j-i+1)
+
+ allocate(character(len=n)::newloc)
+ if(i > 1) then
+ newloc(1:1) = self%location(1:1)
+ end if
+
+ newloc(2:n) = self%location(j+1:len_trim(self%location))
+ deallocate(self%location)
+ self%location => newloc
+ newloc => null()
+
+ end subroutine request_remove_first_path_component
+
subroutine request_destroy(self)
implicit none
@@ -443,6 +556,9 @@ contains
deallocate(self%page)
end if
+ ! Needs to happen before we destroy the cookies and query string
+ call self%clear_token()
+
call self%q%destroy()
call self%c%destroy()
@@ -546,6 +662,25 @@ contains
end subroutine response_set_url
+ subroutine reponse_set_gemini_session_url(resp, str, session_token)
+ !use request_utils, only: gemini_session_link_url
+ implicit none
+
+ class(response), intent(inout)::resp
+ character(*), intent(in)::str
+ character(len=:), pointer, intent(in)::session_token
+ character(len=:), pointer::session_url
+
+ if(associated(session_token)) then
+ session_url => gemini_session_link_url(str, session_token)
+ call resp%set_url(session_url)
+ deallocate(session_url)
+ else
+ call resp%set_url(str)
+ end if
+
+ end subroutine reponse_set_gemini_session_url
+
subroutine response_temp_file_contents(resp, str, mimetype)
use utilities, only: generate_temporary_filename
implicit none
diff --git a/captain/templates/index.gmi b/captain/templates/index.gmi
index bbcd8d2..4c4625a 100644
--- a/captain/templates/index.gmi
+++ b/captain/templates/index.gmi
@@ -1,14 +1,14 @@
-
# {{ title }} - I'm Levitating!
The Levitating Captain interface for the {{ project }} project.
-=> /index.gmi Home
-=> /releases.gmi Releases
-=> /jobs.gmi Jobs
-=> /players.gmi Players
-=> /instructions.gmi Instructions
-=> /about.gmi About
+=> {{ session_flag }}/index.gmi Home
+=> {{ session_flag }}/releases.gmi Releases
+=> {{ session_flag }}/jobs.gmi Jobs
+=> {{ session_flag }}/players.gmi Players
+=> {{ session_flag }}/instructions.gmi Instructions
+=> {{ session_flag }}/about.gmi About
+=> {{ session_flag }}{{ user_link_page }} {{ user_link_text }}
{{ contents }}
@@ -17,4 +17,4 @@ The Levitating Captain interface for the {{ project }} project.
-------------------------------------------------------------------------------
```
-Copyright 2021 Approximatrix, LLC
+Copyright 2022 Approximatrix, LLC
diff --git a/captain/web.f90 b/captain/web.f90
index 9d8fc3c..4242083 100644
--- a/captain/web.f90
+++ b/captain/web.f90
@@ -51,25 +51,7 @@ contains
end if
end function method
-
- function request_is_authenticated(req)
- use captain_db
- use server_response
- implicit none
-
- class(request), intent(in)::req
- logical::request_is_authenticated
-
- request_is_authenticated = .false.
- if(associated(req%token)) then
- request_is_authenticated = is_valid_session_db(req%token)
- if(request_is_authenticated) then
- call update_session_db(req%token)
- end if
- end if
-
- end function request_is_authenticated
-
+
subroutine echo_error_stdout(error_code)
implicit none
@@ -95,7 +77,7 @@ contains
call page%assign('project', project)
call page%assign('base_url', req%server)
- if(request_is_authenticated(req)) then
+ if(req%is_authenticated_user()) then
call page%assign('user_link_page', "profile")
call get_session_username_db(req%token, username)
@@ -1011,7 +993,7 @@ contains
use config, only: template_filepath, project
use logging
use server_response, only:request, response
- use http, only: HTTP_CODE_SUCCESS, HTTP_CODE_NOTFOUND
+ use http_codes, only: HTTP_CODE_SUCCESS, HTTP_CODE_NOTFOUND
use request_utils, only: get_job_page_title, handle_instruction_command
use captain_db, only: scan_instructions_for_db, get_session_username_db, destroy_session_db
use utilities, only: build_date
@@ -1031,7 +1013,7 @@ contains
integer::i
logical::authenticated
- authenticated = request_is_authenticated(req)
+ authenticated = req%is_authenticated_user()
if(trim(req%location) == "/" .or. &
trim(req%location) == "/index.html" .or. &
@@ -1211,9 +1193,9 @@ contains
use config, only: template_filepath, global_permissions
use logging
use server_response, only:request, response
- use http, only: HTTP_CODE_FAILURE, HTTP_CODE_SUCCESS
use http_post_utilities
use query_utilities, only: query
+ use http_codes
implicit none
type(request), intent(in)::req
@@ -1300,7 +1282,8 @@ contains
use server_response, only:request, response
use logging
use request_utils
- use http
+ use http_codes
+ use http, only: write_response_headers, write_redirect
use iso_fortran_env, only: output_unit
use utilities, only: echo_file_stdout
implicit none