Files
sm-user-plugin/README.md
2026-10-02 10:57:49 +02:00

8.0 KiB

golem15.user

The shared front-end user plugin for SummerCMS applications: registration, login with JWT sessions, activation and password reset, profile and avatar endpoints.

import _ "git.golem15.com/golem15/sm-user-plugin"

Module git.golem15.com/golem15/sm-user-plugin, package user, plugin ID golem15.user.

Overview

The plugin is a compiled SummerCMS plugin. Its init registers it with the framework, so an application loads it by listing it in summer.yaml; summer build then emits the blank import above into the application's plugins.gen.go.

It owns the users table extensions, user_throttle, jwt_blacklist and golem15_user_organisations, and the JSON API under /_user/api/v1. It is a core plugin shared by several applications, so its routes, payloads, table names, migration IDs and config keys are a contract: change them only in a backwards-compatible way.

Features

  • Registration and activation. POST /register, POST /activate, POST /activate-by-code. Registration can be switched off and throttled; activate_mode is auto, user (a mailed code) or admin, with codes from IssueActivationCode and VerifyActivationCode.
  • JWT sessions. POST /login, GET /fetch, POST /refresh, POST /logout. Boot registers the jwt guard with the framework's bouncer.Registry and exposes it as the jwt.auth middleware for other plugins. Logged-out tokens go to the Postgres jwt_blacklist, which a background sweep prunes.
  • Password reset and change. POST /forgot-password answers the same body for known and unknown addresses; POST /reset-password consumes a code from IssueResetCode. POST /change-password requires the current password.
  • Must-change-password lock. The user:require-password-change console command flags an account. The flag rides on the authenticated principal (bouncer.Principal.MustChangePassword) for guards and other plugins to act on, and a successful POST /change-password clears it.
  • Login throttling. Failed logins are counted per user and IP in user_throttle (CheckAndRecordLogin, RejectIfThrottled) and suspend the account for a configured time.
  • Rate limiting. Every route runs behind the user-api bucket: 120 requests a minute per authenticated user, or per client IP for anonymous calls.
  • Profile. POST /update, POST /marketing-consent, POST /avatar and POST /avatar/remove (the avatar is stored through the framework's attachment storage).
  • Organisations. The Organisation model and the organisation_id and organisation_role user columns, as a foreign-key target for other plugins.
  • Mail templates. Activation, reset and reactivation mails in Polish and English (golem15.user::mail.*, layout golem15.user::mail.layouts.user); the locale follows the user (ResolveMailLocale).
  • Extension events. RegisterEvent fires after a user row is created; GetApiArrayEvent lets other plugins merge keys into the user payload.
  • OAuth providers. GET /oauth-providers lists configured social login providers (none yet: it returns an empty list).

Usage

Mount the plugin in an application as a git submodule at plugins/golem15/user:

git submodule add git@git.golem15.com:golem15/sm-user-plugin.git plugins/golem15/user

Add it to the application's go.work:

use (
	.
	./plugins/golem15/user
)

Require it in the application's go.mod with a local replace:

require git.golem15.com/golem15/sm-user-plugin v0.0.0

replace git.golem15.com/golem15/sm-user-plugin => ./plugins/golem15/user

List it in summer.yaml and run summer build:

module: git.golem15.com/golem15/acme
binary: acme
plugins:
  - id: golem15.user
    module: git.golem15.com/golem15/sm-user-plugin

The plugin's own go.mod replaces the framework with ../../../../summercms.go, so it expects the framework checkout next to the application checkout (<dir>/summercms.go and <dir>/acme/plugins/golem15/user).

Another plugin that needs the signed-in user puts its routes behind the jwt.auth middleware and imports git.golem15.com/golem15/sm-user-plugin/models or .../classes for the user model and helpers.

API reference

All routes are mounted under /_user/api/v1 with the throttle:user-api middleware.

Method and path Handler
POST /login controllers.Login
POST /logout controllers.Logout
GET /fetch controllers.Fetch
POST /refresh controllers.Refresh
POST /register controllers.Register
GET /oauth-providers controllers.OAuthProviders
POST /forgot-password controllers.ForgotPassword
POST /reset-password controllers.ResetPassword
POST /activate controllers.Activate
POST /activate-by-code controllers.ActivateByCode
POST /update controllers.Update
POST /change-password controllers.ChangePassword
POST /marketing-consent controllers.MarketingConsent
POST /avatar controllers.UploadAvatar
POST /avatar/remove controllers.RemoveAvatar

Packages:

  • classes: JWTSecret, GormUsers (the bouncer.UserProvider), LookupByEmail, the activation and reset code helpers, the throttle helpers, mail locale helpers and the two events.
  • models: User, Throttle, Organisation.
  • updates: the gormigrate migrations, returned by updates.All().
  • console: RequirePasswordChange, the user:require-password-change command.

Configuration

Defaults live in config/config.yaml and are merged under golem15.user.*. Any key can be overridden by an environment variable in the framework's SUMMER_ form.

Key Default Meaning
golem15.user.jwt.secret "" HS256 signing secret. Required: boot fails while it is empty. Set it with SUMMER_GOLEM15__USER__JWT__SECRET; there is no production default.
golem15.user.jwt.ttl 60 Access token lifetime in minutes.
golem15.user.jwt.refresh_ttl 20160 Refresh window in minutes.
golem15.user.jwt.blacklist_grace 0 Grace period in seconds for a blacklisted token.
golem15.user.jwt.leeway 0 Clock leeway in seconds (declared for parity, not read yet).
golem15.user.jwt.blacklist_sweep_interval 10m How often expired blacklist rows are deleted (a Go duration).
golem15.user.activation.require_activation true Whether a new account must be activated before login.
golem15.user.activation.activate_mode auto auto, user (mailed code) or admin.
golem15.user.activation.reset_ttl_minutes 60 Password reset code lifetime.
golem15.user.activation.activation_ttl_hours 72 Activation code lifetime.
golem15.user.registration.allow_registration true Whether POST /register accepts new accounts.
golem15.user.registration.use_register_throttle true Whether registration is limited to three accounts per IP an hour.
golem15.user.throttle.attempt_limit 5 Failed logins before suspension.
golem15.user.throttle.suspension_minutes 15 Suspension length.
golem15.user.throttle.use_throttle true Whether login throttling is on.
golem15.user.password.bcrypt_cost 10 bcrypt cost for new password hashes.
golem15.user.password.min_length 8 Minimum password length.

CLI commands

Command Purpose
user:require-password-change <email> Flag an account so its next session must set a new password.

Dependencies

  • The SummerCMS framework, git.golem15.com/golem15/summercms (modules backpack, bonfire, bouncer, pact, party, surf and others).
  • gormigrate, GORM, golang-jwt and gocloud.dev, as pinned in go.mod.
  • testcontainers-go for the Postgres-backed tests.

Testing

go test -short ./...    # fast run, no Docker
go test ./...           # full run, starts Postgres with testcontainers (needs Docker)

TestRegisterCORSPath reads the host application's config/http.yaml, so run the tests inside an application checkout where the plugin is mounted at plugins/golem15/user.