models

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SettingLoginCaptchaEnabled  = "login_captcha_enabled"
	SettingLoginCaptchaAPIKeyID = "login_captcha_api_key_id"
)

Variables

This section is empty.

Functions

func CleanupExpired

func CleanupExpired(db *gorm.DB) (int64, error)

func CreateHISSample

func CreateHISSample(db *gorm.DB, s *HISSample) error

CreateHISSample persists one raw HIS observation.

func DeleteAPIKey

func DeleteAPIKey(db *gorm.DB, id int64) error

func GenerateHMACSecret

func GenerateHMACSecret() (string, error)

func GenerateKeyID

func GenerateKeyID() (string, error)

func GetAllKeysStatsSummary

func GetAllKeysStatsSummary(db *gorm.DB) (map[int64]KeyStatsSummary, error)

GetAllKeysStatsSummary returns all-time totals grouped by API key ID.

func GetLoginCaptchaEnabled

func GetLoginCaptchaEnabled(db *gorm.DB) (bool, error)

GetLoginCaptchaEnabled returns whether the login CAPTCHA is enabled.

func GetSetting

func GetSetting(db *gorm.DB, key string) (string, error)

GetSetting retrieves a single setting value by key. Returns ("", nil) if the key does not exist.

func IncrementChallengesIssued

func IncrementChallengesIssued(db *gorm.DB, apiKeyID int64) error

func IncrementCountryVerification

func IncrementCountryVerification(db *gorm.DB, apiKeyID int64, country string, ok bool) error

IncrementCountryVerification records one verification outcome for the key/day/country, atomically bumping the matching counter via UPSERT. country may be empty (unknown source); it is stored as-is so totals reconcile.

func IncrementHISObservation

func IncrementHISObservation(db *gorm.DB, apiKeyID int64, botSuspected bool) error

IncrementHISObservation records one HIS Monitor sample for the key/day: it always bumps the observation count and additionally bumps the bot-suspected count when the sample was flagged. It never affects verification outcomes.

func IncrementVerificationsFail

func IncrementVerificationsFail(db *gorm.DB, apiKeyID int64) error

func IncrementVerificationsOK

func IncrementVerificationsOK(db *gorm.DB, apiKeyID int64) error

func IsConsumed

func IsConsumed(db *gorm.DB, challenge string) (bool, error)

func MarkConsumed

func MarkConsumed(db *gorm.DB, challenge string, apiKeyID int64, expiresAt time.Time) error

func PruneHISSamples

func PruneHISSamples(db *gorm.DB, cutoff time.Time) (int64, error)

PruneHISSamples deletes samples recorded before cutoff, returning the count removed. Called periodically by the cleanup worker to bound storage.

func RotateHMACSecret

func RotateHMACSecret(db *gorm.DB, id int64) (string, error)

func SetSetting

func SetSetting(db *gorm.DB, key, value string) error

SetSetting upserts a setting value.

func UpdateAPIKey

func UpdateAPIKey(db *gorm.DB, id int64, params UpdateAPIKeyParams) error

Types

type APIKey

type APIKey struct {
	ID            int64  `gorm:"primaryKey;autoIncrement" json:"id"`
	KeyID         string `gorm:"not null;uniqueIndex;size:32" json:"key_id"`
	HMACSecret    string `gorm:"not null" json:"hmac_secret,omitempty"`
	Name          string `gorm:"not null;default:''" json:"name"`
	Domain        string `gorm:"not null;default:''" json:"domain"`
	MaxNumber     int64  `gorm:"not null;default:100000" json:"max_number"`
	ExpireSeconds int    `gorm:"not null;default:300" json:"expire_seconds"`
	Algorithm     string `gorm:"not null;default:'SHA-256'" json:"algorithm"`
	// RateLimitPerMin caps how many /api/v1 requests this key accepts per minute,
	// aggregated across all clients. 0 means unlimited (only the global per-IP
	// limiter applies).
	RateLimitPerMin int `gorm:"not null;default:0" json:"rate_limit_per_min"`
	// AdaptiveDifficulty, when set, raises the proof-of-work MaxNumber above this
	// key's configured base for clients (by IP) that request challenges at an
	// abusive rate, capped server-side. MaxNumber stays the floor.
	AdaptiveDifficulty bool `gorm:"not null;default:false" json:"adaptive_difficulty"`
	// HISSampling, when set, persists each scored HIS observation for this key
	// (raw aggregates + score) so enforcement thresholds can be calibrated on
	// real traffic. Samples are pruned after the configured retention window.
	HISSampling bool      `gorm:"not null;default:false" json:"his_sampling"`
	Enabled     bool      `gorm:"not null;default:true" json:"enabled"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

APIKey represents a site-specific API key used to generate and verify ALTCHA challenges.

func CreateAPIKey

func CreateAPIKey(db *gorm.DB, name, domain string, maxNumber int64, expireSeconds int, algorithm string) (*APIKey, error)

func EnsureLoginCaptchaAPIKey

func EnsureLoginCaptchaAPIKey(db *gorm.DB) (*APIKey, error)

EnsureLoginCaptchaAPIKey returns the existing login CAPTCHA API key, or creates a dedicated one if none exists yet.

func GetAPIKeyByID

func GetAPIKeyByID(db *gorm.DB, id int64) (*APIKey, error)

func GetAPIKeyByKeyID

func GetAPIKeyByKeyID(db *gorm.DB, keyID string) (*APIKey, error)

func ListAPIKeys

func ListAPIKeys(db *gorm.DB) ([]APIKey, error)

type AdminUser

type AdminUser struct {
	ID           int64     `gorm:"primaryKey;autoIncrement" json:"id"`
	Username     string    `gorm:"not null;uniqueIndex;size:64;default:admin" json:"username"`
	PasswordHash string    `gorm:"not null" json:"-"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

AdminUser represents a dashboard admin account.

type ConsumedChallenge

type ConsumedChallenge struct {
	ID         int64     `gorm:"primaryKey;autoIncrement"`
	Challenge  string    `gorm:"not null;uniqueIndex;size:256"`
	APIKeyID   int64     `gorm:"not null;index"`
	ExpiresAt  time.Time `gorm:"not null"`
	ConsumedAt time.Time `gorm:"not null;autoCreateTime"`
}

ConsumedChallenge tracks used challenge tokens to prevent replay attacks.

type CountryStat

type CountryStat struct {
	Country           string `json:"country"`
	VerificationsOK   int    `json:"verifications_ok"`
	VerificationsFail int    `json:"verifications_fail"`
	Total             int    `json:"total"`
}

CountryStat is an aggregated per-country row for the dashboard.

func GetCountryStats

func GetCountryStats(db *gorm.DB, apiKeyID *int64, days, limit int) ([]CountryStat, error)

GetCountryStats returns per-country verification totals over the last `days`, ordered by total descending and capped at `limit`. When apiKeyID is nil the totals span every key; otherwise they are scoped to that one key.

type DailyCountryStat

type DailyCountryStat struct {
	ID                int64  `gorm:"primaryKey;autoIncrement" json:"-"`
	APIKeyID          int64  `gorm:"not null;uniqueIndex:idx_key_date_country" json:"api_key_id"`
	Date              string `gorm:"not null;uniqueIndex:idx_key_date_country;size:10" json:"date"`
	Country           string `gorm:"uniqueIndex:idx_key_date_country;size:2" json:"country"`
	VerificationsOK   int    `gorm:"not null;default:0" json:"verifications_ok"`
	VerificationsFail int    `gorm:"not null;default:0" json:"verifications_fail"`
}

DailyCountryStat holds per-key, per-day, per-country verification counters. Country is an ISO 3166-1 alpha-2 code resolved from the client IP at request time; the raw IP is never stored (privacy-first). An empty Country means the source could not be geolocated (private/loopback/unknown) and is bucketed as "unknown" so per-country totals reconcile with the overall verification ones.

type DailyStat

type DailyStat struct {
	ID                int64  `gorm:"primaryKey;autoIncrement" json:"-"`
	APIKeyID          int64  `gorm:"not null;uniqueIndex:idx_key_date" json:"api_key_id"`
	Date              string `gorm:"not null;uniqueIndex:idx_key_date;size:10" json:"date"`
	ChallengesIssued  int    `gorm:"not null;default:0" json:"challenges_issued"`
	VerificationsOK   int    `gorm:"not null;default:0" json:"verifications_ok"`
	VerificationsFail int    `gorm:"not null;default:0" json:"verifications_fail"`
	// HIS (Human Interaction Signature) Monitor counters: every scored sample
	// increments HISObservations; samples at/above the suspect threshold also
	// increment HISBotSuspected. Monitor mode only records — it never blocks.
	HISObservations int `gorm:"not null;default:0" json:"his_observations"`
	HISBotSuspected int `gorm:"not null;default:0" json:"his_bot_suspected"`
}

DailyStat holds per-key per-day counters.

func GetKeyStats

func GetKeyStats(db *gorm.DB, apiKeyID int64, days int) ([]DailyStat, error)

type HISCalibration

type HISCalibration struct {
	Samples        int              `json:"samples"`
	Suspected      int              `json:"suspected"`
	Threshold      float64          `json:"threshold"`
	ScoreHistogram []HISScoreBucket `json:"score_histogram"`
	AvgDurationMs  float64          `json:"avg_duration_ms"`
	AvgPointer     float64          `json:"avg_pointer_events"`
	NoMotionPct    float64          `json:"no_motion_pct"`
}

HISCalibration summarizes stored samples to help tune the scoring heuristic and pick an enforcement threshold. The histogram shows how scores distribute relative to the current suspect threshold; the signal averages explain what is driving them.

func GetHISCalibration

func GetHISCalibration(db *gorm.DB, apiKeyID *int64, days int, threshold float64) (*HISCalibration, error)

GetHISCalibration aggregates stored samples over the last `days`. When apiKeyID is nil it spans every key; otherwise it is scoped to that key.

type HISSample

type HISSample struct {
	ID           int64     `gorm:"primaryKey;autoIncrement" json:"id"`
	APIKeyID     int64     `gorm:"not null;index:idx_his_sample_key_created" json:"api_key_id"`
	CreatedAt    time.Time `gorm:"index:idx_his_sample_key_created" json:"created_at"`
	Score        float64   `gorm:"not null" json:"score"`
	BotSuspected bool      `gorm:"not null" json:"bot_suspected"`

	DurationMs         int     `json:"duration_ms"`
	TimeToFirstMs      int     `json:"time_to_first_ms"`
	PointerEvents      int     `json:"pointer_events"`
	PointerDistance    float64 `json:"pointer_distance"`
	Scrolls            int     `json:"scrolls"`
	Touches            int     `json:"touches"`
	Keydowns           int     `json:"keydowns"`
	KeyIntervalStdevMs float64 `json:"key_interval_stdev_ms"`
}

HISSample is a single stored Human Interaction Signature observation: the raw privacy-preserving aggregates produced by the client collector together with the score the heuristic assigned. Samples are only written for keys that opt in (APIKey.HISSampling) and are pruned after a configurable retention window. They exist so HIS enforcement thresholds can be calibrated against real traffic before any blocking is enabled.

Like the rest of HIS, this stores no coordinates, timestamps, key contents or IP — only counts, distances, durations and timing variance.

type HISScoreBucket

type HISScoreBucket struct {
	Lo    float64 `json:"lo"`
	Hi    float64 `json:"hi"`
	Count int     `json:"count"`
}

HISScoreBucket is one column of the score histogram: scores in [lo, hi).

type KeyStatsSummary

type KeyStatsSummary struct {
	APIKeyID          int64  `json:"api_key_id"`
	ChallengesIssued  int    `json:"challenges_issued"`
	VerificationsOK   int    `json:"verifications_ok"`
	VerificationsFail int    `json:"verifications_fail"`
	HISObservations   int    `json:"his_observations"`
	HISBotSuspected   int    `json:"his_bot_suspected"`
	LastUsedAt        string `json:"last_used_at"`
}

KeyStatsSummary holds all-time totals for a single API key.

type Setting

type Setting struct {
	Key       string    `gorm:"primaryKey;size:64" json:"key"`
	Value     string    `gorm:"not null;default:''" json:"value"`
	UpdatedAt time.Time `json:"updated_at"`
}

Setting holds a key-value configuration entry.

type StatsOverview

type StatsOverview struct {
	TotalChallenges        int           `json:"total_challenges"`
	TotalVerificationsOK   int           `json:"total_verifications_ok"`
	TotalVerificationsFail int           `json:"total_verifications_fail"`
	TotalHISObservations   int           `json:"total_his_observations"`
	TotalHISBotSuspected   int           `json:"total_his_bot_suspected"`
	ActiveKeys             int           `json:"active_keys"`
	Daily                  []DailyStat   `json:"daily"`
	Countries              []CountryStat `json:"countries"`
}

StatsOverview holds aggregated statistics for the dashboard.

func GetStatsOverview

func GetStatsOverview(db *gorm.DB, days int) (*StatsOverview, error)

type UpdateAPIKeyParams

type UpdateAPIKeyParams struct {
	Name               string
	Domain             string
	MaxNumber          int64
	ExpireSeconds      int
	Algorithm          string
	RateLimitPerMin    int
	AdaptiveDifficulty bool
	HISSampling        bool
	Enabled            bool
}

UpdateAPIKeyParams holds the fields for updating an API key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL