Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
#	config.default.yml
#	config/config.go
#	coverage/coverage.out
#	go.mod
#	go.sum
#	models/user.go
#	models/view/login.go
#	routes/login.go
#	views/signup.tpl.html
  • Loading branch information
muety committed Apr 7, 2024
2 parents 9097bc5 + 7f070ae commit 8e30116
Show file tree
Hide file tree
Showing 57 changed files with 1,134 additions and 320 deletions.
Binary file added .github/assets/servercamp_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up Go 1.x
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ^1.21
id: go
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up Go 1.x
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ^1.21
id: go
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Publish Docker Image

on:
workflow_dispatch:
release:
types:
- published
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
steps:

- name: Set up Go 1.x
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ^1.21
cache: false
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ argument) or via environment variables. Here is an overview of all options.
| `app.datetime_format` /<br>`WAKAPI_DATETIME_FORMAT` | `Mon, 02 Jan 2006 15:04` | Go time format strings to format human-readable datetime (see [`Time.Format`](https://pkg.go.dev/time#Time.Format)) |
| `app.support_contact` /<br>`WAKAPI_SUPPORT_CONTACT` | `hostmaster@wakapi.dev` | E-Mail address to display as a support contact on the page |
| `app.data_retention_months` /<br>`WAKAPI_DATA_RETENTION_MONTHS` | `-1` | Maximum retention period in months for user data (heartbeats) (-1 for unlimited) |
| `app.max_inactive_months` /<br>`WAKAPI_MAX_INACTIVE_MONTHS` | `12` | Maximum number of inactive months after which to delete user accounts without data (-1 for unlimited) |
| `server.port` /<br> `WAKAPI_PORT` | `3000` | Port to listen on |
| `server.listen_ipv4` /<br> `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (set to `'-'` to disable IPv4) |
| `server.listen_ipv6` /<br> `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (set to `'-'` to disable IPv6) |
Expand All @@ -176,11 +177,15 @@ argument) or via environment variables. Here is an overview of all options.
| `security.cookie_max_age` /<br> `WAKAPI_COOKIE_MAX_AGE` | `172800` | Lifetime of authentication cookies in seconds or `0` to use [Session](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Define_the_lifetime_of_a_cookie) cookies |
| `security.allow_signup` /<br> `WAKAPI_ALLOW_SIGNUP` | `true` | Whether to enable user registration |
| `security.signup_captcha` /<br> `WAKAPI_SIGNUP_CAPTCHA` | `false` | Whether the registration form requires solving a CAPTCHA |
| `security.invite_codes` /<br> `WAKAPI_INVITE_CODES` | `true` | Whether to enable registration by invite codes. Primarily useful if registration is disabled (invite-only server). |
| `security.disable_frontpage` /<br> `WAKAPI_DISABLE_FRONTPAGE` | `false` | Whether to disable landing page (useful for personal instances) |
| `security.expose_metrics` /<br> `WAKAPI_EXPOSE_METRICS` | `false` | Whether to expose Prometheus metrics under `/api/metrics` |
| `security.trusted_header_auth` /<br> `WAKAPI_TRUSTED_HEADER_AUTH` | `false` | Whether to enable trusted header authentication for reverse proxies (see [#534](https://github.com/muety/wakapi/issues/534)). **Use with caution!** |
| `security.trusted_header_auth_key` /<br> `WAKAPI_TRUSTED_HEADER_AUTH_KEY` | `Remote-User` | Header field for trusted header authentication. **Caution:** proxy must be configured to strip this header from client requests! |
| `security.trust_reverse_proxy_ips` /<br> `WAKAPI_TRUST_REVERSE_PROXY_IPS` | - | Comma-separated list IPv4 or IPv6 addresses of reverse proxies to trust to handle authentication. |
| `security.signup_max_rate` /<br> `WAKAPI_SIGNUP_MAX_RATE` | `5/1h` | Rate limiting config for signup endpoint in format `<max_req>/<multiplier><unit>`, where `unit` is one of `s`, `m` or `h`. |
| `security.login_max_rate` /<br> `WAKAPI_LOGIN_MAX_RATE` | `10/1m` | Rate limiting config for login endpoint in format `<max_req>/<multiplier><unit>`, where `unit` is one of `s`, `m` or `h`. |
| `security.password_reset_max_rate` /<br> `WAKAPI_PASSWORD_RESET_MAX_RATE` | `5/1h` | Rate limiting config for password reset endpoint in format `<max_req>/<multiplier><unit>`, where `unit` is one of `s`, `m` or `h`. |
| `db.host` /<br> `WAKAPI_DB_HOST` | - | Database host |
| `db.port` /<br> `WAKAPI_DB_PORT` | - | Database port |
| `db.socket` /<br> `WAKAPI_DB_SOCKET` | - | Database UNIX socket (alternative to `host`) (for MySQL only) |
Expand Down Expand Up @@ -596,9 +601,9 @@ appreciated and boosts my motivation to keep improving Wakapi!
I highly appreciate the efforts of **[@alanhamlett](https://github.com/alanhamlett)** and the WakaTime team and am
thankful for their software being open source.

Moreover, thanks to **[Frachtwerk](https://frachtwerk.de)** for sponsoring server infrastructure for Wakapi.dev.
Moreover, thanks to **[server.camp](https://server.camp)** for sponsoring server infrastructure for Wakapi.dev.

![](.github/assets/frachtwerk_logo.png)
<img src=".github/assets/servercamp_logo.png" width="220px" />

## 📓 License

Expand Down
5 changes: 5 additions & 0 deletions config.default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ app:
import_batch_size: 50 # maximum number of heartbeats to insert into the database within one transaction
heartbeat_max_age: '4320h' # maximum acceptable age of a heartbeat (see https://pkg.go.dev/time#ParseDuration)
data_retention_months: -1 # maximum retention period on months for user data (heartbeats) (-1 for infinity)
max_inactive_months: 12 # maximum months of inactivity before deleting user accounts
custom_languages:
vue: Vue
jsx: JSX
Expand Down Expand Up @@ -67,12 +68,16 @@ security:
cookie_max_age: 172800
allow_signup: true
signup_captcha: false
invite_codes: true # whether to enable invite codes for overriding disabled signups
disable_frontpage: false
expose_metrics: false
enable_proxy: false # only intended for production instance at wakapi.dev
trusted_header_auth: false # whether to enable trusted header auth for reverse proxies, use with caution!! (https://github.com/muety/wakapi/issues/534)
trusted_header_auth_key: Remote-User # header field for trusted header auth (warning: your proxy must correctly strip this header from client requests!!)
trust_reverse_proxy_ips: # single ip address of the reverse proxy which you trust to pass headers for authentication
signup_max_rate: 5/1h # signup endpoint rate limit pattern
login_max_rate: 10/1m # login endpoint rate limit pattern
password_reset_max_rate: 5/1h # password reset endpoint rate limit pattern

sentry:
dsn: # leave blank to disable sentry integration
Expand Down
41 changes: 41 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
KeyFirstHeartbeat = "first_heartbeat"
KeySubscriptionNotificationSent = "sub_reminder"
KeyNewsbox = "newsbox"
KeyInviteCode = "invite"

SessionKeyDefault = "default"

Expand Down Expand Up @@ -93,6 +94,7 @@ type appConfig struct {
CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"`
DataRetentionMonths int `yaml:"data_retention_months" default:"-1" env:"WAKAPI_DATA_RETENTION_MONTHS"`
DataCleanupDryRun bool `yaml:"data_cleanup_dry_run" default:"false" env:"WAKAPI_DATA_CLEANUP_DRY_RUN"` // for debugging only
MaxInactiveMonths int `yaml:"max_inactive_months" default:"-1" env:"WAKAPI_MAX_INACTIVE_MONTHS"`
AvatarURLTemplate string `yaml:"avatar_url_template" default:"api/avatar/{username_hash}.svg" env:"WAKAPI_AVATAR_URL_TEMPLATE"`
SupportContact string `yaml:"support_contact" default:"hostmaster@wakapi.dev" env:"WAKAPI_SUPPORT_CONTACT"`
DateFormat string `yaml:"date_format" default:"Mon, 02 Jan 2006" env:"WAKAPI_DATE_FORMAT"`
Expand All @@ -104,6 +106,7 @@ type appConfig struct {
type securityConfig struct {
AllowSignup bool `yaml:"allow_signup" default:"true" env:"WAKAPI_ALLOW_SIGNUP"`
SignupCaptcha bool `yaml:"signup_captcha" default:"false" env:"WAKAPI_SIGNUP_CAPTCHA"`
InviteCodes bool `yaml:"invite_codes" default:"true" env:"WAKAPI_INVITE_CODES"`
ExposeMetrics bool `yaml:"expose_metrics" default:"false" env:"WAKAPI_EXPOSE_METRICS"`
EnableProxy bool `yaml:"enable_proxy" default:"false" env:"WAKAPI_ENABLE_PROXY"` // only intended for production instance at wakapi.dev
DisableFrontpage bool `yaml:"disable_frontpage" default:"false" env:"WAKAPI_DISABLE_FRONTPAGE"`
Expand All @@ -114,6 +117,9 @@ type securityConfig struct {
TrustedHeaderAuth bool `yaml:"trusted_header_auth" default:"false" env:"WAKAPI_TRUSTED_HEADER_AUTH"`
TrustedHeaderAuthKey string `yaml:"trusted_header_auth_key" default:"Remote-User" env:"WAKAPI_TRUSTED_HEADER_AUTH_KEY"`
TrustReverseProxyIps string `yaml:"trust_reverse_proxy_ips" default:"" env:"WAKAPI_TRUST_REVERSE_PROXY_IPS"` // comma-separated list of trusted reverse proxy ips
SignupMaxRate string `yaml:"signup_max_rate" default:"5/1h" env:"WAKAPI_SIGNUP_MAX_RATE"`
LoginMaxRate string `yaml:"login_max_rate" default:"10/1m" env:"WAKAPI_LOGIN_MAX_RATE"`
PasswordResetMaxRate string `yaml:"password_reset_max_rate" default:"5/1h" env:"WAKAPI_PASSWORD_RESET_MAX_RATE"`
SecureCookie *securecookie.SecureCookie `yaml:"-"`
SessionKey []byte `yaml:"-"`
trustReverseProxyIpParsed []net.IP
Expand Down Expand Up @@ -337,6 +343,41 @@ func (c *securityConfig) TrustReverseProxyIPs() []net.IP {
return c.trustReverseProxyIpParsed
}

func (c *securityConfig) GetSignupMaxRate() (int, time.Duration) {
return c.parseRate(c.SignupMaxRate)
}

func (c *securityConfig) GetLoginMaxRate() (int, time.Duration) {
return c.parseRate(c.LoginMaxRate)
}

func (c *securityConfig) GetPasswordResetMaxRate() (int, time.Duration) {
return c.parseRate(c.PasswordResetMaxRate)
}

func (c *securityConfig) parseRate(rate string) (int, time.Duration) {
pattern := regexp.MustCompile("(\\d+)/(\\d+)([smh])")
matches := pattern.FindStringSubmatch(rate)
if len(matches) != 4 {
logbuch.Fatal("failed to parse rate pattern '%s'", rate)
}

limit, _ := strconv.Atoi(matches[1])
window, _ := strconv.Atoi(matches[2])

var windowScale time.Duration
switch matches[3] {
case "s":
windowScale = time.Second
case "m":
windowScale = time.Minute
case "h":
windowScale = time.Hour
}

return limit, time.Duration(window) * windowScale
}

func (c *dbConfig) IsSQLite() bool {
return c.Dialect == "sqlite3"
}
Expand Down
49 changes: 26 additions & 23 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ require (
github.com/alexedwards/argon2id v1.0.0
github.com/alitto/pond v1.8.3
github.com/dchest/captcha v1.0.0
github.com/duke-git/lancet/v2 v2.2.9
github.com/duke-git/lancet/v2 v2.3.0
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
github.com/emersion/go-smtp v0.20.2
github.com/emvi/logbuch v1.2.0
github.com/getsentry/sentry-go v0.27.0
github.com/glebarez/sqlite v1.10.0
github.com/go-chi/chi/v5 v5.0.11
github.com/gorilla/schema v1.2.1
github.com/glebarez/sqlite v1.11.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/httprate v0.9.0
github.com/gorilla/schema v1.3.0
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.2.2
github.com/hashicorp/golang-lru v1.0.2
Expand All @@ -29,37 +30,39 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/robfig/cron/v3 v3.0.1
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/stripe/stripe-go/v74 v74.30.0
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.3
go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.19.0
gorm.io/driver/mysql v1.5.4
gorm.io/driver/postgres v1.5.6
golang.org/x/crypto v0.21.0
gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.7
gorm.io/driver/sqlite v1.5.5
gorm.io/driver/sqlserver v1.5.3
gorm.io/gorm v1.25.7
gorm.io/gorm v1.25.9
)

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/spec v0.20.14 // indirect
github.com/go-openapi/swag v0.22.9 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.3 // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
Expand All @@ -68,22 +71,22 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/microsoft/go-mssqldb v1.6.0 // indirect
github.com/microsoft/go-mssqldb v1.7.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/stretchr/objx v0.5.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/swaggo/files v1.0.1 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/image v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
golang.org/x/tools v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/libc v1.49.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.29.1 // indirect
modernc.org/sqlite v1.29.5 // indirect
)
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func main() {
subscriptionHandler := routes.NewSubscriptionHandler(userService, mailService, keyValueService)
projectsHandler := routes.NewProjectsHandler(userService, heartbeatService)
homeHandler := routes.NewHomeHandler(userService, keyValueService)
loginHandler := routes.NewLoginHandler(userService, mailService)
loginHandler := routes.NewLoginHandler(userService, mailService, keyValueService)
imprintHandler := routes.NewImprintHandler(keyValueService)
leaderboardHandler := condition.TernaryOperator[bool, routes.Handler](config.App.LeaderboardEnabled, routes.NewLeaderboardHandler(userService, leaderboardService), routes.NewNoopHandler())

Expand Down
5 changes: 5 additions & 0 deletions mocks/user_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func (m *UserServiceMock) GetAll() ([]*models.User, error) {
return args.Get(0).([]*models.User), args.Error(1)
}

func (m *UserServiceMock) GetAllMapped() (map[string]*models.User, error) {
args := m.Called()
return args.Get(0).(map[string]*models.User), args.Error(1)
}

func (m *UserServiceMock) GetMany(s []string) ([]*models.User, error) {
args := m.Called(s)
return args.Get(0).([]*models.User), args.Error(1)
Expand Down
Loading

0 comments on commit 8e30116

Please sign in to comment.