Skip to content

Commit

Permalink
Simplify password authentication
Browse files Browse the repository at this point in the history
fusion's existing session handling logic is a bit incorrect but it still works due to workarounds.

sessions.NewCookieStore is supposed to receive a secure, random value:

https://pkg.go.dev/github.com/gorilla/sessions#section-readme

fusion currently passes it a fixed string baked into source. The result is that an attacker can forge a valid session token (they know the secret key) and send it to a fusion server, and the server would accept it.

The reason this isn't a problem right now is that fusion works around this by storing an additional copy of the password in the session and then checking the password on each authenticated request.

The current implementation works, but I think it's a bit more dangerous and complicated than what's ideal. Ideally, an attacker shouldn't be able to forge a session token at all.

This change makes it so that the session cookie store derives session cookies using the server password as the secure secret. That way, an attacker can't forge session tokens unless they know the server password, but if they know the server password, it's game over anyway.

Because we can trust the session store to reject requests with invalid session tokens, we don't need to store additional copies of the server password. We can trust that if the request has a valid session token, it's a token that the server created.
  • Loading branch information
mtlynch committed Dec 28, 2024
1 parent 9b3616c commit 1f6a9c1
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 20 deletions.
8 changes: 2 additions & 6 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func Run() {
r.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
Timeout: 30 * time.Second,
}))
r.Use(session.Middleware(sessions.NewCookieStore([]byte("fusion"))))
r.Use(session.Middleware(sessions.NewCookieStore([]byte(conf.Conf.Password))))
r.Pre(middleware.RemoveTrailingSlash())
r.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
Expand All @@ -83,11 +83,7 @@ func Run() {

authed := r.Group("/api", func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ok, err := loginAPI.Check(c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized)
}
if !ok {
if _, err := session.Get(sessionName, c); err != nil {

Check failure on line 86 in api/api.go

View workflow job for this annotation

GitHub Actions / test

undefined: sessionName
return echo.NewHTTPError(http.StatusUnauthorized)
}
return next(c)
Expand Down
14 changes: 0 additions & 14 deletions api/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,18 @@ func (s Session) Create(c echo.Context) error {
sess.Options.SameSite = http.SameSiteDefaultMode
}

sess.Values["password"] = conf.Conf.Password
if err := sess.Save(c.Request(), c.Response()); err != nil {
return c.NoContent(http.StatusInternalServerError)
}

return c.NoContent(http.StatusCreated)
}

func (s Session) Check(c echo.Context) (bool, error) {
sess, err := session.Get(sessionKeyName, c)
if err != nil {
return false, err
}
v, ok := sess.Values["password"]
if !ok {
return false, nil
}
return v == conf.Conf.Password, nil
}

func (s Session) Delete(c echo.Context) error {
sess, err := session.Get(sessionKeyName, c)
if err != nil {
return err
}
sess.Values["password"] = ""
sess.Options.MaxAge = -1
if err := sess.Save(c.Request(), c.Response()); err != nil {
return c.NoContent(http.StatusInternalServerError)
Expand Down

0 comments on commit 1f6a9c1

Please sign in to comment.