Skip to content

Commit

Permalink
Add API token authentication.
Browse files Browse the repository at this point in the history
  • Loading branch information
knadh committed Oct 13, 2024
1 parent 10f1c38 commit bf0b500
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 3 deletions.
12 changes: 12 additions & 0 deletions cmd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"strings"
"time"

"github.com/knadh/listmonk/models"
"github.com/labstack/echo/v4"
Expand Down Expand Up @@ -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})
}
26 changes: 24 additions & 2 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"sync"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/labstack/echo/v4"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit bf0b500

Please sign in to comment.