refactor(feeds): extract atom feed builder + basic-auth validator for reuse
Splits the transport-agnostic cores out of the v1 echo handlers so the v2 Huma endpoints can share them: - AuthenticateFeedToken(s, username, password) holds the token validation (prefix/length guard, owner match, feeds scope, bot rejection); BasicAuth now creates the session and delegates to it. - BuildNotificationsAtomFeed(s, u) renders the Atom XML; NotificationsAtomFeed reads the context user and delegates to it. - AtomContentType is shared so both transports set the same header. The v1 handlers keep identical observable behavior.
This commit is contained in:
parent
5c0e266042
commit
be8a092fe3
|
|
@ -23,9 +23,9 @@ import (
|
|||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error) {
|
||||
|
|
@ -50,35 +50,48 @@ func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error)
|
|||
return u, nil
|
||||
}
|
||||
|
||||
// BasicAuth authenticates feed requests. Only API tokens are accepted —
|
||||
// password and LDAP credentials are rejected outright because feed URLs are
|
||||
// commonly exported, shared, or cached by feed readers.
|
||||
func BasicAuth(c *echo.Context, username, password string) (bool, error) {
|
||||
// AuthenticateFeedToken validates feed credentials against an existing session.
|
||||
// Only API tokens are accepted — password and LDAP credentials are rejected
|
||||
// outright because feed URLs are commonly exported, shared, or cached by feed
|
||||
// readers. It returns the authenticated user, or nil for any rejection so
|
||||
// callers can treat "invalid" and "unknown" identically.
|
||||
func AuthenticateFeedToken(s *xorm.Session, username, password string) (*user.User, error) {
|
||||
if !strings.HasPrefix(password, models.APITokenPrefix) {
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
// GetTokenFromTokenString slices password[len-8:] without a length check,
|
||||
// so a stray "tk_" or other short prefix-only string would panic before
|
||||
// the credentials could be rejected. Real tokens are far longer than
|
||||
// prefix+8, so anything shorter is invalid by construction.
|
||||
if len(password) < len(models.APITokenPrefix)+8 {
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u, err := checkAPIToken(s, username, password)
|
||||
if err != nil {
|
||||
log.Errorf("Error during API token auth for feeds: %v", err)
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
if u == nil {
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
if u.IsBot() {
|
||||
log.Warningf("Feed auth rejected for bot user %d", u.ID)
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// BasicAuth authenticates feed requests for echo's BasicAuth middleware. The
|
||||
// validation logic is shared with the v2 handler via AuthenticateFeedToken.
|
||||
func BasicAuth(c *echo.Context, username, password string) (bool, error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u, err := AuthenticateFeedToken(s, username, password)
|
||||
if err != nil || u == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
c.Set("userBasicAuth", u)
|
||||
|
|
|
|||
|
|
@ -30,24 +30,22 @@ import (
|
|||
|
||||
"github.com/gorilla/feeds"
|
||||
"github.com/labstack/echo/v5"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const feedItemLimit = 50
|
||||
|
||||
// NotificationsAtomFeed serves the authenticated user's notifications as an
|
||||
// Atom feed. Notifications are not marked as read by being fetched here.
|
||||
func NotificationsAtomFeed(c *echo.Context) error {
|
||||
u, ok := c.Get("userBasicAuth").(*user.User)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
// AtomContentType is the content type of the notifications Atom feed. Shared so
|
||||
// the v1 echo handler and the v2 Huma op set the same header.
|
||||
const AtomContentType = "application/atom+xml; charset=utf-8"
|
||||
|
||||
// BuildNotificationsAtomFeed renders the user's latest notifications as Atom XML
|
||||
// against an existing session. Notifications are not marked as read by being
|
||||
// fetched here. Shared by the v1 echo handler and the v2 Huma op.
|
||||
func BuildNotificationsAtomFeed(s *xorm.Session, u *user.User) (string, error) {
|
||||
rows, _, _, err := notifications.GetNotificationsForUser(s, u.ID, feedItemLimit, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicURL := config.ServicePublicURL.GetString()
|
||||
|
|
@ -85,11 +83,25 @@ func NotificationsAtomFeed(c *echo.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
atom, err := feed.ToAtom()
|
||||
return feed.ToAtom()
|
||||
}
|
||||
|
||||
// NotificationsAtomFeed serves the authenticated user's notifications as an
|
||||
// Atom feed. Notifications are not marked as read by being fetched here.
|
||||
func NotificationsAtomFeed(c *echo.Context) error {
|
||||
u, ok := c.Get("userBasicAuth").(*user.User)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
atom, err := BuildNotificationsAtomFeed(s, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Response().Header().Set(echo.HeaderContentType, "application/atom+xml; charset=utf-8")
|
||||
c.Response().Header().Set(echo.HeaderContentType, AtomContentType)
|
||||
return c.String(http.StatusOK, atom)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue