Code review (round 1)

This commit is contained in:
binwiederhier 2023-02-08 22:57:10 -05:00
parent 7706bd9845
commit b37cf02a6e
12 changed files with 78 additions and 23 deletions

View File

@ -33,7 +33,7 @@ var (
var cmdTier = &cli.Command{ var cmdTier = &cli.Command{
Name: "tier", Name: "tier",
Usage: "Manage/show tiers", Usage: "Manage/show tiers",
UsageText: "ntfy tier [list|add|remove] ...", UsageText: "ntfy tier [list|add|change|remove] ...",
Flags: flagsTier, Flags: flagsTier,
Before: initConfigFileInputSourceFunc("config", flagsUser, initLogFunc), Before: initConfigFileInputSourceFunc("config", flagsUser, initLogFunc),
Category: categoryServer, Category: categoryServer,
@ -58,7 +58,7 @@ var cmdTier = &cli.Command{
}, },
Description: `Add a new tier to the ntfy user database. Description: `Add a new tier to the ntfy user database.
Tiers can be used to grant users higher limits based, such as daily message limits, attachment size, or Tiers can be used to grant users higher limits, such as daily message limits, attachment size, or
make it possible for users to reserve topics. make it possible for users to reserve topics.
This is a server-only command. It directly reads from the user.db as defined in the server config This is a server-only command. It directly reads from the user.db as defined in the server config

View File

@ -197,8 +197,12 @@ func (e *Event) globalLevelWithOverride() Level {
} }
for field, override := range ov { for field, override := range ov {
value, exists := e.fields[field] value, exists := e.fields[field]
if exists && value == override.value { if exists {
if value == override.value {
return override.level return override.level
} else if fmt.Sprintf("%v", value) == override.value {
return override.level
}
} }
} }
return l return l

View File

@ -93,7 +93,7 @@ func SetLevel(newLevel Level) {
} }
// SetLevelOverride adds a log override for the given field // SetLevelOverride adds a log override for the given field
func SetLevelOverride(field string, value any, level Level) { func SetLevelOverride(field string, value string, level Level) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
overrides[field] = &levelOverride{value: value, level: level} overrides[field] = &levelOverride{value: value, level: level}

View File

@ -29,6 +29,7 @@ func TestLog_TagContextFieldFields(t *testing.T) {
SetOutput(&out) SetOutput(&out)
SetFormat(JSONFormat) SetFormat(JSONFormat)
SetLevelOverride("tag", "stripe", DebugLevel) SetLevelOverride("tag", "stripe", DebugLevel)
SetLevelOverride("number", "5", DebugLevel)
Tag("mytag"). Tag("mytag").
Field("field2", 123). Field("field2", 123).
@ -49,8 +50,13 @@ func TestLog_TagContextFieldFields(t *testing.T) {
Time(time.Unix(456, 123000000).UTC()). Time(time.Unix(456, 123000000).UTC()).
Debug("Subscription status %s", "active") Debug("Subscription status %s", "active")
Field("number", 5).
Time(time.Unix(777, 001000000).UTC()).
Debug("The number 5 is an int, but the level override is a string")
expected := `{"time":"1970-01-01T00:02:03.999Z","level":"INFO","message":"hi there phil","field1":"value1","field2":123,"tag":"mytag"} expected := `{"time":"1970-01-01T00:02:03.999Z","level":"INFO","message":"hi there phil","field1":"value1","field2":123,"tag":"mytag"}
{"time":"1970-01-01T00:07:36.123Z","level":"DEBUG","message":"Subscription status active","error":"some error","error_code":123,"stripe_customer_id":"acct_123","stripe_subscription_id":"sub_123","tag":"stripe","user_id":"u_abc","visitor_ip":"1.2.3.4"} {"time":"1970-01-01T00:07:36.123Z","level":"DEBUG","message":"Subscription status active","error":"some error","error_code":123,"stripe_customer_id":"acct_123","stripe_subscription_id":"sub_123","tag":"stripe","user_id":"u_abc","visitor_ip":"1.2.3.4"}
{"time":"1970-01-01T00:12:57Z","level":"DEBUG","message":"The number 5 is an int, but the level override is a string","number":5}
` `
require.Equal(t, expected, out.String()) require.Equal(t, expected, out.String())
} }

View File

@ -55,6 +55,8 @@ func ToLevel(s string) Level {
return WarnLevel return WarnLevel
case "ERROR": case "ERROR":
return ErrorLevel return ErrorLevel
case "FATAL":
return FatalLevel
default: default:
return InfoLevel return InfoLevel
} }
@ -101,6 +103,6 @@ type Contexter interface {
type Context map[string]any type Context map[string]any
type levelOverride struct { type levelOverride struct {
value any value string
level Level level Level
} }

View File

@ -19,7 +19,7 @@ const (
DefaultFirebaseKeepaliveInterval = 3 * time.Hour // ~control topic (Android), not too frequently to save battery DefaultFirebaseKeepaliveInterval = 3 * time.Hour // ~control topic (Android), not too frequently to save battery
DefaultFirebasePollInterval = 20 * time.Minute // ~poll topic (iOS), max. 2-3 times per hour (see docs) DefaultFirebasePollInterval = 20 * time.Minute // ~poll topic (iOS), max. 2-3 times per hour (see docs)
DefaultFirebaseQuotaExceededPenaltyDuration = 10 * time.Minute // Time that over-users are locked out of Firebase if it returns "quota exceeded" DefaultFirebaseQuotaExceededPenaltyDuration = 10 * time.Minute // Time that over-users are locked out of Firebase if it returns "quota exceeded"
DefaultStripePriceCacheDuration = time.Hour // Time to keep Stripe prices cached in memory before a refresh is needed DefaultStripePriceCacheDuration = 3 * time.Hour // Time to keep Stripe prices cached in memory before a refresh is needed
) )
// Defines all global and per-visitor limits // Defines all global and per-visitor limits
@ -150,7 +150,7 @@ func NewConfig() *Config {
CacheBatchTimeout: 0, CacheBatchTimeout: 0,
AuthFile: "", AuthFile: "",
AuthStartupQueries: "", AuthStartupQueries: "",
AuthDefault: user.NewPermission(true, true), AuthDefault: user.PermissionReadWrite,
AuthBcryptCost: user.DefaultUserPasswordBcryptCost, AuthBcryptCost: user.DefaultUserPasswordBcryptCost,
AuthStatsQueueWriterInterval: user.DefaultUserStatsQueueWriterInterval, AuthStatsQueueWriterInterval: user.DefaultUserStatsQueueWriterInterval,
AttachmentCacheDir: "", AttachmentCacheDir: "",

View File

@ -39,6 +39,7 @@ import (
- api - api
- HIGH Self-review - HIGH Self-review
- MEDIUM: Test for expiring messages after reservation removal - MEDIUM: Test for expiring messages after reservation removal
- MEDIUM: disallowed-topics
- MEDIUM: Test new token endpoints & never-expiring token - MEDIUM: Test new token endpoints & never-expiring token
- LOW: UI: Flickering upgrade banner when logging in - LOW: UI: Flickering upgrade banner when logging in

View File

@ -233,13 +233,31 @@
# stripe-secret-key: # stripe-secret-key:
# stripe-webhook-key: # stripe-webhook-key:
# Log level, can be "trace", "debug", "info", "warn" or "error" # Logging options
# This option can be hot-reloaded by calling "kill -HUP $pid" or "systemctl reload ntfy".
# #
# FIXME # By default, ntfy logs to the console (stderr), with a "info" log level, and in a human-readable text format.
# ntfy supports five different log levels, can also write to a file, log as JSON, and even supports granular
# log level overrides for easier debugging. Some options (log-level and log-level-overrides) can be hot reloaded
# by calling "kill -HUP $pid" or "systemctl reload ntfy".
# #
# Be aware that "debug" (and particularly "trace"") can be VERY CHATTY. Only turn them on for # - log-format defines the output format, can be "text" (default) or "json"
# debugging purposes, or your disk will fill up quickly. # - log-file is a filename to write logs to. If this is not set, ntfy logs to stderr.
# - log-level defines the default log level, can be one of "trace", "debug", "info" (default), "warn" or "error".
# Be aware that "debug" (and particularly "trace") can be VERY CHATTY. Only turn them on briefly for debugging purposes.
# - log-level-overrides lets you override the log level if certain fields match. This is incredibly powerful
# for debugging certain parts of the system (e.g. only the account management, or only a certain visitor).
# This is an array of strings in the format "field=value -> level", e.g. "tag=manager -> trace".
# Warning: Using log-level-overrides has a performance penalty. Only use it for temporary debugging.
#
# Example (good for production):
# log-level: info
# log-format: json
# log-file: /var/log/ntfy.log
#
# Example level overrides (for debugging, only use temporarily):
# log-level-overrides:
# - "tag=manager -> trace"
# - "visitor_ip=1.2.3.4 -> debug"
# #
# log-level: info # log-level: info
# log-level-overrides: # log-level-overrides:

View File

@ -40,7 +40,7 @@ func (u *User) Admin() bool {
// User returns true if the user is a regular user, not an admin // User returns true if the user is a regular user, not an admin
func (u *User) User() bool { func (u *User) User() bool {
return !u.Admin() return u != nil && u.Role == RoleUser
} }
// Auther is an interface for authentication and authorization // Auther is an interface for authentication and authorization

13
user/types_test.go Normal file
View File

@ -0,0 +1,13 @@
package user
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestPermission(t *testing.T) {
require.Equal(t, PermissionReadWrite, NewPermission(true, true))
require.Equal(t, PermissionRead, NewPermission(true, false))
require.Equal(t, PermissionWrite, NewPermission(false, true))
require.Equal(t, PermissionDenyAll, NewPermission(false, false))
}

View File

@ -26,8 +26,12 @@ type LookupCache[T any] struct {
mu sync.Mutex mu sync.Mutex
} }
// LookupFunc is a function that is called by the LookupCache if the underlying
// value is out-of-date. It returns the new value, or an error.
type LookupFunc[T any] func() (T, error)
// NewLookupCache creates a new LookupCache with a given time-to-live (TTL) // NewLookupCache creates a new LookupCache with a given time-to-live (TTL)
func NewLookupCache[T any](lookup func() (T, error), ttl time.Duration) *LookupCache[T] { func NewLookupCache[T any](lookup LookupFunc[T], ttl time.Duration) *LookupCache[T] {
return &LookupCache[T]{ return &LookupCache[T]{
value: nil, value: nil,
lookup: lookup, lookup: lookup,

View File

@ -2,6 +2,7 @@ package util
import ( import (
"errors" "errors"
"golang.org/x/time/rate"
"io" "io"
"net/netip" "net/netip"
"os" "os"
@ -245,6 +246,12 @@ func TestMinMax(t *testing.T) {
require.Equal(t, 50, MinMax(50, 10, 99)) require.Equal(t, 50, MinMax(50, 10, 99))
} }
func TestMax(t *testing.T) {
require.Equal(t, 9, Max(1, 9))
require.Equal(t, 9, Max(9, 1))
require.Equal(t, rate.Every(time.Minute), Max(rate.Every(time.Hour), rate.Every(time.Minute)))
}
func TestPointerFunctions(t *testing.T) { func TestPointerFunctions(t *testing.T) {
i, s, ti := Int(99), String("abc"), Time(time.Unix(99, 0)) i, s, ti := Int(99), String("abc"), Time(time.Unix(99, 0))
require.Equal(t, 99, *i) require.Equal(t, 99, *i)