mirror of
https://github.com/ditatompel/xmr-remote-nodes.git
synced 2024-12-22 11:39:23 +00:00
feat: Store hashed user IP address when submitting new node
This feature added to help trace spammers. The IP address stored with one-way hash + salt to maintain user privacy.
This commit is contained in:
parent
d19e5844b0
commit
48a25bece0
6 changed files with 73 additions and 8 deletions
|
@ -16,6 +16,11 @@ IPV6_CAPABLE=false
|
||||||
# #############
|
# #############
|
||||||
APP_URL="https://xmr.ditatompel.com" # URL where user can access the web UI, don't put trailing slash
|
APP_URL="https://xmr.ditatompel.com" # URL where user can access the web UI, don't put trailing slash
|
||||||
|
|
||||||
|
# APP_SECRET is random 64-character hex string that give us 32 random bytes.
|
||||||
|
# For now, this used for ip address salt, but may be useful for another feature
|
||||||
|
# in the future. You can achieve this using `openssl rand -hex 32`.
|
||||||
|
APP_SECRET=
|
||||||
|
|
||||||
# Fiber Config
|
# Fiber Config
|
||||||
APP_PREFORK=false
|
APP_PREFORK=false
|
||||||
APP_HOST="127.0.0.1"
|
APP_HOST="127.0.0.1"
|
||||||
|
|
|
@ -14,6 +14,7 @@ type App struct {
|
||||||
|
|
||||||
// configuration for server
|
// configuration for server
|
||||||
URL string // URL where user can access the web UI, don't put trailing slash
|
URL string // URL where user can access the web UI, don't put trailing slash
|
||||||
|
Secret string // random 64-character hex string that give us 32 random bytes
|
||||||
|
|
||||||
// fiber specific config
|
// fiber specific config
|
||||||
Prefork bool
|
Prefork bool
|
||||||
|
@ -61,6 +62,7 @@ func LoadApp() {
|
||||||
|
|
||||||
// server configuration
|
// server configuration
|
||||||
app.URL = os.Getenv("APP_URL")
|
app.URL = os.Getenv("APP_URL")
|
||||||
|
app.Secret = os.Getenv("APP_SECRET")
|
||||||
|
|
||||||
// fiber specific config
|
// fiber specific config
|
||||||
app.Host = os.Getenv("APP_HOST")
|
app.Host = os.Getenv("APP_HOST")
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
type migrateFn func(*DB) error
|
type migrateFn func(*DB) error
|
||||||
|
|
||||||
var dbMigrate = [...]migrateFn{v1, v2, v3, v4}
|
var dbMigrate = [...]migrateFn{v1, v2, v3, v4, v5}
|
||||||
|
|
||||||
func MigrateDb(db *DB) error {
|
func MigrateDb(db *DB) error {
|
||||||
version := getSchemaVersion(db)
|
version := getSchemaVersion(db)
|
||||||
|
@ -287,3 +287,18 @@ func v4(db *DB) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func v5(db *DB) error {
|
||||||
|
// table: tbl_node
|
||||||
|
slog.Debug("[DB] Adding additional columns to tbl_node")
|
||||||
|
_, err := db.Exec(`
|
||||||
|
ALTER TABLE tbl_node
|
||||||
|
ADD COLUMN submitter_iphash CHAR(64) NOT NULL DEFAULT ''
|
||||||
|
COMMENT 'hashed IP address who submitted the node'
|
||||||
|
AFTER date_entered;`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (s *fiberServer) addNodeHandler(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
moneroRepo := monero.New()
|
moneroRepo := monero.New()
|
||||||
if err := moneroRepo.Add(f.Protocol, f.Hostname, uint(f.Port)); err != nil {
|
if err := moneroRepo.Add(c.IP(), s.secret, f.Protocol, f.Hostname, uint(f.Port)); err != nil {
|
||||||
handler := adaptor.HTTPHandler(templ.Handler(views.Alert("error", err.Error())))
|
handler := adaptor.HTTPHandler(templ.Handler(views.Alert("error", err.Error())))
|
||||||
return handler(c)
|
return handler(c)
|
||||||
}
|
}
|
||||||
|
@ -354,7 +354,7 @@ func (s *fiberServer) addNodeAPI(c *fiber.Ctx) error {
|
||||||
hostname := c.FormValue("hostname")
|
hostname := c.FormValue("hostname")
|
||||||
|
|
||||||
moneroRepo := monero.New()
|
moneroRepo := monero.New()
|
||||||
if err := moneroRepo.Add(protocol, hostname, uint(port)); err != nil {
|
if err := moneroRepo.Add(c.IP(), s.secret, protocol, hostname, uint(port)); err != nil {
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
|
|
|
@ -10,6 +10,7 @@ type fiberServer struct {
|
||||||
*fiber.App
|
*fiber.App
|
||||||
db *database.DB
|
db *database.DB
|
||||||
url string
|
url string
|
||||||
|
secret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns a new fiber server
|
// NewServer returns a new fiber server
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package monero
|
package monero
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -54,6 +56,7 @@ type Node struct {
|
||||||
Latitude float64 `json:"latitude" db:"lat"`
|
Latitude float64 `json:"latitude" db:"lat"`
|
||||||
Longitude float64 `json:"longitude" db:"lon"`
|
Longitude float64 `json:"longitude" db:"lon"`
|
||||||
DateEntered int64 `json:"date_entered,omitempty" db:"date_entered"`
|
DateEntered int64 `json:"date_entered,omitempty" db:"date_entered"`
|
||||||
|
SubmitterIPHash string `json:"submitter_iphash,omitempty" db:"submitter_iphash"`
|
||||||
LastChecked int64 `json:"last_checked" db:"last_checked"`
|
LastChecked int64 `json:"last_checked" db:"last_checked"`
|
||||||
FailedCount uint `json:"failed_count,omitempty" db:"failed_count"`
|
FailedCount uint `json:"failed_count,omitempty" db:"failed_count"`
|
||||||
LastCheckStatus types.JSONText `json:"last_check_statuses" db:"last_check_status"`
|
LastCheckStatus types.JSONText `json:"last_check_statuses" db:"last_check_status"`
|
||||||
|
@ -176,7 +179,35 @@ func (r *moneroRepo) Nodes(q QueryNodes) (Nodes, error) {
|
||||||
|
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
SELECT
|
SELECT
|
||||||
*
|
id,
|
||||||
|
hostname,
|
||||||
|
ip_addr,
|
||||||
|
port,
|
||||||
|
protocol,
|
||||||
|
is_tor,
|
||||||
|
is_i2p,
|
||||||
|
is_available,
|
||||||
|
nettype,
|
||||||
|
height,
|
||||||
|
adjusted_time,
|
||||||
|
database_size,
|
||||||
|
difficulty,
|
||||||
|
version,
|
||||||
|
uptime,
|
||||||
|
estimate_fee,
|
||||||
|
asn,
|
||||||
|
asn_name,
|
||||||
|
country,
|
||||||
|
country_name,
|
||||||
|
city,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
date_entered,
|
||||||
|
last_checked,
|
||||||
|
last_check_status,
|
||||||
|
cors_capable,
|
||||||
|
ipv6_only,
|
||||||
|
ip_addresses
|
||||||
FROM
|
FROM
|
||||||
tbl_node
|
tbl_node
|
||||||
%s
|
%s
|
||||||
|
@ -190,7 +221,7 @@ func (r *moneroRepo) Nodes(q QueryNodes) (Nodes, error) {
|
||||||
return nodes, err
|
return nodes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
func (r *moneroRepo) Add(submitterIP, salt, protocol, hostname string, port uint) error {
|
||||||
if protocol != "http" && protocol != "https" {
|
if protocol != "http" && protocol != "https" {
|
||||||
return errors.New("Invalid protocol, must one of or HTTP/HTTPS")
|
return errors.New("Invalid protocol, must one of or HTTP/HTTPS")
|
||||||
}
|
}
|
||||||
|
@ -270,6 +301,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
lat,
|
lat,
|
||||||
lon,
|
lon,
|
||||||
date_entered,
|
date_entered,
|
||||||
|
submitter_iphash,
|
||||||
last_checked,
|
last_checked,
|
||||||
last_check_status,
|
last_check_status,
|
||||||
ip_addresses,
|
ip_addresses,
|
||||||
|
@ -288,6 +320,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
|
?,
|
||||||
?
|
?
|
||||||
)`,
|
)`,
|
||||||
protocol,
|
protocol,
|
||||||
|
@ -300,6 +333,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
time.Now().Unix(),
|
time.Now().Unix(),
|
||||||
|
hashIPWithSalt(submitterIP, salt),
|
||||||
0,
|
0,
|
||||||
string(statusDb),
|
string(statusDb),
|
||||||
ips,
|
ips,
|
||||||
|
@ -397,6 +431,14 @@ func (r *moneroRepo) Countries() ([]Countries, error) {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hashIPWithSalt hashes IP address with salt designed for checksumming, but
|
||||||
|
// still maintain user privacy, this is NOT cryptographic security.
|
||||||
|
func hashIPWithSalt(ip, salt string) string {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write([]byte(salt + ip)) // Combine salt and IP
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
// ParseNodeStatuses parses JSONText into [5]int
|
// ParseNodeStatuses parses JSONText into [5]int
|
||||||
// Used this to parse last_check_status for templ engine
|
// Used this to parse last_check_status for templ engine
|
||||||
func ParseNodeStatuses(statuses types.JSONText) [5]int {
|
func ParseNodeStatuses(statuses types.JSONText) [5]int {
|
||||||
|
|
Loading…
Reference in a new issue