Герхард PICCORO Lenz McKAY
cfb83aab12
* using https://codeberg.org/minenux/minetest-mod-formspecs * using https://codeberg.org/minenux/minetest-mod-auth_rx * changes: ** provide a way to initialize files if there is not one currently doe snot touch the auth.txt file neither converted ** solves: close: https://codeberg.org/minenux/minetest-mod-auth_rx/issues/6 ** solved: close: https://bitbucket.org/sorcerykid/auth_rx/issues/7 ** init the files when are fresh install, still do not convert from auth.txt ** player object check for problematic joins on inpcomplete auth process ** close fixed https://codeberg.org/minenux/minetest-mod-auth_rx/issues/2 ** added missing depends formspecs (it work without in basics but, some commands needs) ** we will later aded formspecs checks to made optional
448 lines
20 KiB
Plaintext
448 lines
20 KiB
Plaintext
minetest ActiveFormspecs
|
|
========================
|
|
|
|
**active formspecs** self-contained API that provides formspecs secure session
|
|
tracking, formspecs session-based state tables, and localized event handling of
|
|
formspecs
|
|
|
|
Information
|
|
------------
|
|
|
|
This mod is named `formspecs` an improved version of dinamic secured forms
|
|
|
|
![](screenshot.png?raw=true)
|
|
|
|
ActiveFormspecs (named `formspecs` is a self-contained API that provides secure session tracking,
|
|
session-based state tables, and localized event handling of formspecs for individual mods as well
|
|
as entire games. It evolved out of a recurring need for secure "node formspecs" on my
|
|
server, while avoiding the burden of "reinventing the wheel" with every new project.
|
|
|
|
## Tecnical info
|
|
|
|
Since I've had many requests for source code from the Just Test Tribute subgame,
|
|
I finally decided to release ActiveFormspecs with instructions and code examples
|
|
so that other mod authors can start making use of this framework as well.
|
|
It's easy to install and use, and arguably a more robust alternative to the
|
|
builtin formspecs API.
|
|
|
|
ActiveFormspecs is intended to be compatible with all versions of Minetest as possible,
|
|
currently tested with 0.4.16 and 5.2.
|
|
|
|
It has been in continuous use on my server since December 2016 with only minor revisions.
|
|
So it should prove secure and stable enough for any production environment, as long as
|
|
you follow the instructions below.
|
|
|
|
#### Dependences
|
|
|
|
none, core minetest api
|
|
|
|
#### Overview:
|
|
|
|
ActiveFormspecs is a framework that abstracts the builtin formspec API of Minetest.
|
|
It is intended to address a number of known security issues related to formspecs:
|
|
|
|
* **Secure Session Tracking**
|
|
Formspec names have been deprecated as they can be easily forged. Now each
|
|
formspec session is assigned a unique session ID. Due to the persistent nature
|
|
of the Minetest client-server protocol (unlike HTTP, for example), all session
|
|
tracking is performed server-side. Negotiation and validation with the client
|
|
is entirely unnecessary. Thus, integrity of the session ID is always guaranteed.
|
|
|
|
* **Session-Based State Table**
|
|
Since the session ID token is retained throughout the lifetime of the formspec, it is therefore possible
|
|
to update a formspec dynamically (e.g. in response to an event) with contextual data spanning multiple
|
|
instances. This data is stored server-side via a session-based state table and it can even be initialized
|
|
from within the formspec string itself using a new "hidden" element.
|
|
|
|
* **Localized Event Handling**
|
|
The minetest.register_on_player_receive_fields( ) method has also been deprecated. Instead, each formspec
|
|
is assigned its own callback function at runtime, which allows for completely localized event handling.
|
|
This callback function is invoked after any event associated with the formspec
|
|
(hence the moniker "ActiveFormspecs"). Both the meta table and form fields are passed as arguments.
|
|
|
|
#### Progress and status
|
|
|
|
https://forum.minetest.net/viewtopic.php?f=9&t=19303
|
|
|
|
The project is a WIP and will be undergoing continuous development based upon your suggestions as well
|
|
as my personal needs. Version 3.0 is already underway, and I am planning to introduce substantial
|
|
improvements to the core functionality. New features and bug-fixes will be announced here as they become
|
|
available. During significant milestones, I will include a roadmap so as to gauge your feedback about
|
|
long-term goals. I will make every effort to ensure backward compatibility, when possible.
|
|
|
|
#### Usage Instructions:
|
|
|
|
An interactive form session monitor can be accessed in-game via the /fs chat command (requires "server" privilege).
|
|
A realtime summary of form sessions is displayed along with navigation buttons and related statistics.
|
|
|
|
![](screenshot.png?raw=true)
|
|
|
|
The summary is sorted chronologically and divided into four columns per player:
|
|
* player - the name of the player viewing the formspec
|
|
* origin - the mod (or node, if attached) that created the formspec
|
|
* idletime - the elapsed time since a form-related event or signal
|
|
* lifetime - the elapsed time since the form was first opened
|
|
|
|
A detached formspec can be opened using the minetest.create_form( ) method which returns a hashed session
|
|
token (guaranteed to be unique):
|
|
|
|
* minetest.create_form( state, player_name, formspec, on_close, signal )
|
|
* state - an optional session-based state-table (can be nil)
|
|
* player_name - a valid player name
|
|
* formspec - a standard formspec string
|
|
* on_close - an optional callback function to be invoked after an event or signal
|
|
* signal - an optional signal to pass to the on_close( ) callback function
|
|
|
|
The form can subsequently be re-opened or closed using the following methods:
|
|
|
|
* minetest.update_form( player_name, formspec )
|
|
* player_name - a valid player name
|
|
* formspec - a standard formspec string
|
|
* minetest.destroy_form( player_name )
|
|
* player_name - a valid player name
|
|
|
|
The associated on_close( ) callback function will be automatically invoked with three arguments during a
|
|
form-related event or signal, including closure. The return value is currently ignored, but in the future
|
|
it may allow for additional behavior.
|
|
|
|
* on_close( state, player, fields )
|
|
* state - the session-based state table
|
|
* player - the player object
|
|
* fields - the table of submitted fields
|
|
|
|
In the event of abnormal form session termination, the callback function will receive a signal indicating
|
|
the specific condition that led to the closure. One of the following values will be stored in the "quit"
|
|
field:
|
|
* minetest.FORMSPEC_SIGEXIT (player clicked exit button or pressed esc key)
|
|
* minetest.FORMSPEC_SIGQUIT (player logged off)
|
|
* minetest.FORMSPEC_SIGKILL (player was killed)
|
|
* minetest.FORMSPEC_SIGTERM (server is shutting down)
|
|
* minetest.FORMSPEC_SIGPROC (programmatic closure)
|
|
* minetest.FORMSPEC_SIGTIME (timeout reached)
|
|
* minetest.FORMSPEC_SIGHOLD (child form opened)
|
|
* minetest.FORMSPEC_SIGCONT (child form closed)
|
|
|
|
A non-trappable SIGSTOP signal can also be passed to minetest.destroy_form( ) or minetest.create_form( )
|
|
to forcibly kill the current form session, thereby avoiding recursion in callbacks during a procedural
|
|
form closure.
|
|
|
|
```
|
|
local function open_alertbox( player_name, message )
|
|
local function get_formspec( )
|
|
local formspec = <generate a basic formspec string here>
|
|
|
|
return formspec
|
|
end
|
|
|
|
minetest.create_form( nil, player_name, get_formspec( ), nil, minetest.FORMSPEC_SIGSTOP )
|
|
end
|
|
```
|
|
|
|
When form closure may produce unanticipated side-effects, such as the example above, then the SIGSTOP
|
|
signal can prove essential to avoid or even defer such actions.
|
|
|
|
Cascading forms such as dialog boxes, alerts, etc. can be implemented through the use of the SIGHOLD
|
|
and SIGCONT signals. By passing a SIGHOLD signal to minetest.create_form(), the callback will be
|
|
notified of the signal and the form session will be suspended (this also stops any form timers).
|
|
As soon as the child form is closed, the previous form session will be restored and the callback
|
|
will be notified of a SIGCONT signal. ActiveFormspecs manages all of the session housekeeping behind
|
|
the scenes.
|
|
|
|
Here is an example of a popup alert that, when opened, will temporarily suspend the current form
|
|
session until closed:
|
|
|
|
```
|
|
local function open_alert( player_name, message )
|
|
local get_formspec = function ( )
|
|
local formspec = "size[4,3]"
|
|
.. default.gui_bg_img
|
|
.. string.format( "label[0.5,0.5;%s]", minetest.formspec_escape( message ) )
|
|
.. "button_exit[1.0,2;2.0,1;close;Close]"
|
|
return formspec
|
|
end
|
|
|
|
minetest.create_form( nil, player_name, get_formspec( ), nil, minetest.FORMSPEC_SIGHOLD )
|
|
end
|
|
```
|
|
|
|
One caveat is that the parent form must be updated whenever a SIGCONT signal is received. This is for
|
|
security reasons, given that element states (like dropdowns, scrollbars, etc.) cannot be preserved
|
|
automatically, and some elements may even contain stale data (as with the case of textareas, fields,
|
|
etc.) which should not be preserved anyway. Thankfully, only one line of code is required:
|
|
|
|
```
|
|
if fields.quit == minetest.FORMSPEC_SIGCONT then minetest.update_form( player_name, get_formspec( ) ) end
|
|
```
|
|
|
|
Formspecs are also supported within a node definition. If an on_open( ) method is defined, then it will
|
|
be invoked whenever the player right-clicks the node. The return value must be a standard formspec string,
|
|
or nil to abort.
|
|
|
|
* nodedef.on_open( pos, player, fields )
|
|
* pos - the position of the node (or your own state table)
|
|
* player - the player object
|
|
|
|
The on_close( ) method is invoked during any formspec event or signal. If defined,
|
|
it will receive three arguments. Currently, the return value is ignored, but in the
|
|
future it may allow for additional behavior.
|
|
|
|
* nodedef.on_close( pos, player, fields )
|
|
* pos - the position of the node (or your own state table)
|
|
* player - the player object
|
|
* fields - the table of submitted fields
|
|
|
|
An optional before_open( ) method may also be defined. It should return a custom
|
|
state table to be subsequently passed to the on_open( ) method in place of the
|
|
default "pos" parameter.
|
|
|
|
* nodedef.before_open( pos, node, player )
|
|
* pos - the position of the node (or your own state table)
|
|
* node - the node
|
|
* player - the player object
|
|
|
|
The "hidden" formspec element of allows for a slightly different workflow, by
|
|
presetting the state table from the formspec string itself. The key-value pairs
|
|
are used only for initialization and will never be transmitted to the client.
|
|
An optional data type can be specified within the element as follows:
|
|
* hidden[name;value;string]
|
|
* hidden[name;value;boolean]
|
|
* hidden[name;value;number]
|
|
* hidden[name;value]
|
|
|
|
If the data type is not specified, then no type-conversion will occur. Here are
|
|
some examples:
|
|
|
|
* hidden[pos_x;-240;number]"
|
|
* hidden[wool_color;darkgreen;string]"
|
|
* hidden[is_dead;true;boolean]"
|
|
|
|
Through the use of form timers, it possible to re-open a formspec at regular intervals,
|
|
or even to close the formspec after a specified period. There is no need for complex
|
|
chains of minetest.after( ) or additional globalstep registrations. A form timer persists
|
|
for the duration of a form session.
|
|
|
|
* minetest.get_form_timer( player_name, form_name )
|
|
|
|
Returns a form timer object associated with the form session of a given player.
|
|
Three methods are exposed by the form timer object, providing similar behavior to node timers.
|
|
* timer.start( timeout ) Starts a timer with the given timeout in seconds.
|
|
* timer.stop( ) Cancels a running timer.
|
|
* timer.get_state( ) Returns a table with information about the running timer
|
|
|
|
The table returned including:
|
|
* elapsed - the number of seconds since the timer started
|
|
* remain - the number of seconds until the timer expires
|
|
* overrun - the number of milliseconds overrun for this period (only valid within an on_close( ) callback)
|
|
* counter - the number of periods that have accrued
|
|
|
|
The associated on_close( ) callback function will be notified of timer expiration via a SIG_TIME signal.
|
|
|
|
#### Code examples
|
|
|
|
To better understand this methodology, here are some working code examples. Let's say that we want to register an "uptime" chat command that displays the current server uptime to any player, with an option to automatically refresh the formspec each second.
|
|
|
|
```
|
|
minetest.register_chatcommand( "uptime", {
|
|
description = "View the uptime of the server interactively",
|
|
func = function( player_name, param )
|
|
local is_refresh = true
|
|
|
|
local get_formspec = function( )
|
|
local uptime = minetest.get_server_uptime( )
|
|
|
|
local formspec = "size[4,2]"
|
|
.. string.format( "label[0.5,0.5;%s %d secs]",
|
|
minetest.colorize( "#FFFF00", "Server Uptime:" ), uptime
|
|
)
|
|
.. "checkbox[0.5,1;is_refresh;Auto Refresh;" .. tostring( is_refresh ) .. "]"
|
|
return formspec
|
|
end
|
|
local on_close = function( state, player, fields )
|
|
if fields.quit == minetest.FORMSPEC_SIGTIME then
|
|
minetest.update_form( player_name, get_formspec( ) )
|
|
|
|
elseif fields.is_refresh then
|
|
is_refresh = fields.is_refresh == "true"
|
|
if is_refresh == true then
|
|
minetest.get_form_timer( player_name ).start( 1 )
|
|
else
|
|
minetest.get_form_timer( player_name ).stop( )
|
|
end
|
|
end
|
|
end
|
|
|
|
minetest.create_form( nil, player_name, get_formspec( ), on_close )
|
|
minetest.get_form_timer( player_name ).start( 1 )
|
|
end
|
|
} )
|
|
```
|
|
|
|
Of course, we could implement similar functionality without the need for a chat command. Perhaps we want to display the server uptime only when a privileged player clicks on a Nyan Cat while limiting the total number of refreshes to ten.
|
|
|
|
```
|
|
minetest.register_privilege( "uptime", "View the uptime of the server interactively" )
|
|
|
|
local function open_system_monitor( player_name, is_minutes )
|
|
local view_count = 0
|
|
local view_limit = 10
|
|
|
|
local function get_formspec( )
|
|
local uptime = minetest.get_server_uptime( )
|
|
local formspec = "size[4,3]"
|
|
.. string.format( "label[0.5,0.5;%s %0.1f %s]",
|
|
minetest.colorize( "#FFFF00", "Server Uptime:" ),
|
|
is_minutes and uptime / 60 or uptime,
|
|
is_minutes and "mins" or "secs"
|
|
)
|
|
.. "checkbox[0.5,1;is_minutes;Show Minutes;" .. tostring( is_minutes ) .. "]"
|
|
.. "button[0.5,2;2.5,1;update;Refresh]"
|
|
.. "hidden[view_count;1;number]"
|
|
.. "hidden[view_limit;10;number]"
|
|
return formspec
|
|
end
|
|
|
|
minetest.create_form( nil, player_name, get_formspec( ), function( state, player, fields )
|
|
if not minetest.check_player_privs( player_name, "uptime" ) then -- sanity check
|
|
return
|
|
end
|
|
|
|
if fields.update then
|
|
-- limit the number of refreshes!
|
|
if view_count == view_limit then
|
|
minetest.destroy_form( player_name )
|
|
minetest.chat_send_player( player_name, "You've exceeded the refresh limit." )
|
|
else
|
|
view_count = view_count + 1
|
|
minetest.update_form( player_name, get_formspec( ) )
|
|
end
|
|
|
|
elseif fields.is_minutes then
|
|
is_minutes = fields.is_minutes == "true"
|
|
minetest.update_form( player_name, get_formspec( ) )
|
|
end
|
|
end )
|
|
end
|
|
|
|
minetest.override_item( "nyancat:nyancat", {
|
|
description = "System Monitor",
|
|
|
|
on_rightclick = function( pos, node, player )
|
|
local player_name = player:get_player_name( )
|
|
|
|
if minetest.check_player_privs( player_name, "uptime" ) then
|
|
open_system_monitor( player_name, true )
|
|
else
|
|
minetest.chat_send_player( player_name, "Your privileges are insufficient." )
|
|
end
|
|
end,
|
|
} )
|
|
```
|
|
|
|
Revision History
|
|
----------------------
|
|
|
|
Version 1.0a (15-Dec-2016)
|
|
- initial version within default mod
|
|
|
|
Version 1.1b (16-Dec-2016)
|
|
- added better comments
|
|
|
|
Version 1.2b (18-Dec-2016)
|
|
- renamed public methods
|
|
|
|
Version 1.3b (04-Jan-2017)
|
|
- fixed logic of quit event to require valid session
|
|
|
|
Version 1.4b (26-Jul-2017)
|
|
- added method to update formspec and maintain session
|
|
|
|
Version 2.0 (24-Dec-2017)
|
|
- separated all routines into new mod for public release
|
|
|
|
Version 2.1 (08-Jan-2018)
|
|
- various code refactoring and better comments
|
|
- introduced password hashing of form names
|
|
- improved sanity checks during form submission
|
|
- fully reworked parsing of hidden elements
|
|
- ensured hidden elements are always stripped
|
|
- gave hidden elements default-state behavior
|
|
- localized old node registration functions
|
|
- included player object within form table
|
|
- added signal handling on formspec termination
|
|
- added support for callbacks in node overrides
|
|
|
|
Version 2.2 (19-Jan-2018)
|
|
- introduced player-name arguments for all API calls
|
|
- added signal for programmatic formspec closure
|
|
- ensured callbacks are notified of session resets
|
|
- renamed some local variables to improve clarity
|
|
|
|
Version 2.3 (28-Jan-2018)
|
|
- corrected erroneous value of formspec exit signal
|
|
- removed two experimental form session methods
|
|
- included timestamp and origin within form table
|
|
- added form session validation on destroy and update
|
|
- introduced form timers with start and stop methods
|
|
- created routine to notify callbacks of timeout
|
|
- added support for internal statistical tracking
|
|
- added chat command to view form session summary
|
|
|
|
Version 2.4 (12-Feb-2018)
|
|
- various code refactoring and better comments
|
|
- full rewrite of timer queue for higher precision
|
|
|
|
Version 2.5 (01-Feb-2019)
|
|
- made callback function optional with default no-op
|
|
- added non-trappable form session termination signal
|
|
- properly reset timestamp for lifetime calculation
|
|
|
|
Version 2.6 (02-Feb-2020)
|
|
- added callback to preset state of node formspecs
|
|
- removed experimental property from node definition
|
|
- implemented reverse-lookups for dropdown fields
|
|
- extended dropdown element with optional parameter
|
|
- added signal to suspend and restore form sessions
|
|
- combined element parsers into dedicated function
|
|
- added functions for escaping formspec strings
|
|
- revamped element parsers to ignore malformed tags
|
|
- added conditional pattern matching helper function
|
|
- compatability bumped to Minetest 0.4.15+
|
|
|
|
Compatibility
|
|
----------------------
|
|
|
|
Minetest 0.4.15+ required or 5.X
|
|
|
|
Installation
|
|
----------------------
|
|
|
|
1. Unzip the archive into the mods directory or game mods directory
|
|
2. Get sure to the name of this directory to "formspecs" (if you cloned or decompress)
|
|
3. Add "formspecs" as a dependency to any mods using the API.
|
|
|
|
Source Code License
|
|
----------------------
|
|
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2016-2020, Leslie E. Krause (leslie@searstower.org) aka sorceredkid
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
software and associated documentation files (the "Software"), to deal in the Software
|
|
without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all copies or
|
|
substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
DEALINGS IN THE SOFTWARE.
|
|
|
|
For more details:
|
|
https://opensource.org/licenses/MIT
|