Module Auth
	EnableExplicit
	
	;- Private helpers
	Procedure.s GetFormField(PostData.s, Field.s)
		; Parse a single field from application/x-www-form-urlencoded body
		Protected Count = CountString(PostData, "&") + 1, i
		Protected Pair.s, Key.s
		
		For i = 1 To Count
			Pair = StringField(PostData, i, "&")
			Key  = StringField(Pair, 1, "=")
			If Key = Field
				ProcedureReturn URLDecoder(StringField(Pair, 2, "="))
			EndIf
		Next
		ProcedureReturn ""
	EndProcedure
	
	Procedure SetSessionCookie(*Request, Token.s)
		FastCGI::WriteResponseHeader(*Request, "Set-Cookie",
		                             #SESSION_COOKIE + "=" + Token +
		                             "; Path=/; HttpOnly; SameSite=Strict; Max-Age=" + #SESSION_MAX_AGE)
	EndProcedure
	
	Procedure ClearSessionCookie(*Request)
		FastCGI::WriteResponseHeader(*Request, "Set-Cookie",
		                             #SESSION_COOKIE + "=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0")
	EndProcedure
	
	;- Public handlers
	Procedure HandleLogin(*Request)
		Protected PostData.s = FastCGI::GetPostData(*Request)
		Protected Username.s = GetFormField(PostData, "username")
		Protected Password.s = GetFormField(PostData, "password")
		
		If Username = "" Or Password = ""
			General::RespondJSON(*Request, ~"{\"success\":false,\"error\":\"Missing credentials\"}")
			ProcedureReturn
		EndIf
		
		Protected ValidUser.s = Database::ValidateCredentials(Username, Password)
		
		If ValidUser <> ""
			Protected UserID.i = Database::FindUser(ValidUser)
			Protected Token.s  = Database::CreateSession(UserID, ValidUser)
			SetSessionCookie(*Request, Token)
			FastCGI::WriteResponseHeader(*Request, "Content-Type",  "application/json; charset=utf-8")
			FastCGI::WriteResponseHeader(*Request, "Cache-Control", "no-cache, no-store")
			FastCGI::WriteResponseString(*Request, ~"{\"success\":true,\"username\":\"" + ValidUser + ~"\"}")
			FastCGI::FinishResponse(*Request)
		Else
			; Uniform error - don't reveal which field was wrong
			General::RespondJSON(*Request, ~"{\"success\":false,\"error\":\"Invalid username or password\"}")
		EndIf
	EndProcedure
	
	Procedure HandleLogout(*Request)
		Protected Token.s = FastCGI::GetCookie(*Request, #SESSION_COOKIE)
		If Token <> ""
			Database::DeleteSession(Token)
		EndIf
		ClearSessionCookie(*Request)
		General::RespondJSON(*Request, ~"{\"success\":true}")
	EndProcedure
	
	Procedure HandleCheck(*Request)
		Protected Token.s    = FastCGI::GetCookie(*Request, #SESSION_COOKIE)
		Protected Username.s = ""
		
		If Token <> ""
			Username = Database::ValidateSession(Token)
		EndIf
		
		If Username <> ""
			General::RespondJSON(*Request, ~"{\"authenticated\":true,\"username\":\"" + Username + ~"\"}")
		Else
			General::RespondJSON(*Request, ~"{\"authenticated\":false}")
		EndIf
	EndProcedure
	
	Procedure.s GetSessionUser(*Request)
		Protected Token.s = FastCGI::GetCookie(*Request, #SESSION_COOKIE)
		If Token <> ""
			ProcedureReturn Database::ValidateSession(Token)
		EndIf
		ProcedureReturn ""
	EndProcedure
	
	Procedure HandleChangePassword(*Request)
		Protected Username.s = GetSessionUser(*Request)
		If Username = ""
			General::RespondJSON(*Request, ~"{\"error\":\"Unauthorized\"}", "401 Unauthorized")
			ProcedureReturn
		EndIf
		
		Protected CurrentPwd.s = General::GetPostField(*Request, "current_password")
		Protected NewPwd.s     = General::GetPostField(*Request, "new_password")
		
		If CurrentPwd = "" Or NewPwd = ""
			General::RespondJSON(*Request, ~"{\"error\":\"Missing fields\"}")
			ProcedureReturn
		EndIf
		
		If Database::ValidateCredentials(Username, CurrentPwd) = ""
			General::RespondJSON(*Request, ~"{\"error\":\"Current password is incorrect\"}")
			ProcedureReturn
		EndIf
		
		Protected UserID.i = Database::FindUser(Username)
		Database::ChangePassword(UserID, NewPwd)
		General::RespondJSON(*Request, ~"{\"success\":true}")
	EndProcedure
	
EndModule

; IDE Options = PureBasic 6.30 (Windows - x64)
; CursorPosition = 29
; Folding = B5
; EnableXP
; DPIAware