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