diff --git a/cmd/users.go b/cmd/users.go index 7c3216588..54ddb3c8d 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -4,6 +4,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/knadh/listmonk/models" "github.com/labstack/echo/v4" @@ -176,10 +177,21 @@ func handleLoginUser(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) } + start := time.Now() + _, err := app.core.LoginUser(u.Username, u.Password) if err != nil { return err } + // While realistically the app will only have a tiny fraction of users and get operations + // on the user table will be instantatneous for IDs that exist or not, always respond after + // a minimum wait of 100ms (which is again, realistically, an order of magnitude or two more + // than what it wouldt take to complete the op) to simulate constant-time-comparison to address + // any possible timing attacks. + if ms := time.Now().Sub(start).Milliseconds(); ms < 100 { + time.Sleep(time.Duration(ms)) + } + return c.JSON(http.StatusOK, okResp{true}) } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 993197384..cca8d420d 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "sync" "github.com/coreos/go-oidc/v3/oidc" "github.com/labstack/echo/v4" @@ -48,6 +49,9 @@ type Config struct { } type Auth struct { + tokens map[string]struct{} + mut sync.RWMutex + cfg oauth2.Config verifier *oidc.IDTokenVerifier skipper middleware.Skipper @@ -77,8 +81,26 @@ func New(cfg Config) *Auth { } } -// HandleCallback is the HTTP handler that handles the post-OIDC provider redirect callback. -func (o *Auth) HandleCallback(c echo.Context) error { +// SetTokens remembers a list of string API tokens that are used for authenticating +// API queries. +func (o *Auth) SetTokens(tokens []string) { + o.mut.Lock() + defer o.mut.Unlock() + + o.tokens = make(map[string]struct{}, len(tokens)) + for _, t := range tokens { + o.tokens[t] = struct{}{} + } +} + +// CheckToken validates an API token. +func (o *Auth) CheckToken(token string) bool { + _, ok := o.tokens[token] + return ok +} + +// HandleOIDCCallback is the HTTP handler that handles the post-OIDC provider redirect callback. +func (o *Auth) HandleOIDCCallback(c echo.Context) error { tk, err := o.cfg.Exchange(c.Request().Context(), c.Request().URL.Query().Get("code")) if err != nil { return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("error exchanging token: %v", err)) diff --git a/queries.sql b/queries.sql index cda430768..22bba4d10 100644 --- a/queries.sql +++ b/queries.sql @@ -1051,6 +1051,6 @@ SELECT * FROM users WHERE $1=0 OR id=$1 ORDER BY created_at; -- name: login-user WITH u AS ( - SELECT * FROM users WHERE username=$1 AND status = 'enabled' AND password_login = TRUE + SELECT * FROM users WHERE username=$1 AND status != 'disabled' AND password_login = TRUE ) SELECT * FROM u WHERE CRYPT($2, password) = password;