fix: signed state could not prevent replay attack, use redis to store real state and delete once checked
This commit is contained in:
parent
e24c2296ae
commit
f52450f4c1
@ -2,11 +2,11 @@ package oauth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
userApi "git.0x7f.app/WOJ/woj-server/internal/api/user"
|
userApi "git.0x7f.app/WOJ/woj-server/internal/api/user"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
||||||
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,15 +20,27 @@ func (s *service) CallbackHandler() gin.HandlerFunc {
|
|||||||
// TODO: we are returning e.Response directly here, we should redirect to a trampoline page, passing the response as query string
|
// TODO: we are returning e.Response directly here, we should redirect to a trampoline page, passing the response as query string
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
// verify state
|
// Extract key from cookie
|
||||||
signed, err := c.Cookie(oauthStateCookieName)
|
key, err := c.Cookie(oauthStateCookieName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.Pong[any](c, e.InvalidParameter, nil)
|
e.Pong[any](c, e.InvalidParameter, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := c.Query("state")
|
// Get state from redis
|
||||||
if !utils.SignAndCompare(state, signed, []byte(s.conf.ClientSecret)) {
|
key = fmt.Sprintf(oauthStateKey, key)
|
||||||
|
expected, err := s.cache.Get().Get(context.Background(), key).Result()
|
||||||
|
if err != nil {
|
||||||
|
e.Pong[any](c, e.RedisError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether state is valid, delete it
|
||||||
|
s.cache.Get().Unlink(context.Background(), key)
|
||||||
|
c.SetCookie(oauthStateCookieName, "", -1, "/", "", false, true)
|
||||||
|
|
||||||
|
// Verify state
|
||||||
|
if c.Query("state") != expected {
|
||||||
e.Pong[any](c, e.OAuthStateMismatch, nil)
|
e.Pong[any](c, e.OAuthStateMismatch, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -72,21 +84,21 @@ func (s *service) CallbackHandler() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check User Existence
|
// Check user existence
|
||||||
u, status := s.user.ProfileOrCreate(&user.CreateData{UserName: claims.Email, NickName: claims.Nickname})
|
u, status := s.user.ProfileOrCreate(&user.CreateData{UserName: claims.Email, NickName: claims.Nickname})
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong[any](c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment User Version
|
// Increment user version
|
||||||
version, status := s.user.IncrVersion(u.ID)
|
version, status := s.user.IncrVersion(u.ID)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong[any](c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign JWT Token
|
// Sign JWT token
|
||||||
claim := &model.Claim{
|
claim := &model.Claim{
|
||||||
UID: u.ID,
|
UID: u.ID,
|
||||||
Role: u.Role,
|
Role: u.Role,
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package oauth
|
package oauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
oauthStateCookieName = "oauth_state"
|
oauthStateCookieName = "oauth_state"
|
||||||
|
oauthStateKey = "OAuthState:%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoginHandler
|
// LoginHandler
|
||||||
@ -21,12 +25,18 @@ const (
|
|||||||
func (s *service) LoginHandler() gin.HandlerFunc {
|
func (s *service) LoginHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
state := utils.RandomString(64)
|
state := utils.RandomString(64)
|
||||||
signed := utils.SignString(state, []byte(s.conf.ClientSecret))
|
key := utils.RandomString(16)
|
||||||
url := s.conf.AuthCodeURL(state)
|
|
||||||
|
err := s.cache.Get().Set(context.Background(), fmt.Sprintf(oauthStateKey, key), state, 15*time.Minute).Err()
|
||||||
|
if err != nil {
|
||||||
|
e.Pong[any](c, e.RedisError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.SetSameSite(http.SameSiteStrictMode)
|
c.SetSameSite(http.SameSiteStrictMode)
|
||||||
c.SetCookie(oauthStateCookieName, signed, 15*60, "/", "", false, true)
|
c.SetCookie(oauthStateCookieName, key, 15*60, "/", "", false, true)
|
||||||
|
|
||||||
|
url := s.conf.AuthCodeURL(state)
|
||||||
e.Pong(c, e.Success, url)
|
e.Pong(c, e.Success, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/repo/cache"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
@ -34,6 +35,7 @@ func NewService(i *do.Injector) (Service, error) {
|
|||||||
srv.log = do.MustInvoke[log.Service](i).GetLogger("oauth")
|
srv.log = do.MustInvoke[log.Service](i).GetLogger("oauth")
|
||||||
srv.jwt = do.MustInvoke[jwt.Service](i)
|
srv.jwt = do.MustInvoke[jwt.Service](i)
|
||||||
srv.user = do.MustInvoke[user.Service](i)
|
srv.user = do.MustInvoke[user.Service](i)
|
||||||
|
srv.cache = do.MustInvoke[cache.Service](i)
|
||||||
srv.enabled = false
|
srv.enabled = false
|
||||||
|
|
||||||
conf := do.MustInvoke[config.Service](i).GetConfig()
|
conf := do.MustInvoke[config.Service](i).GetConfig()
|
||||||
@ -66,6 +68,7 @@ type service struct {
|
|||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
jwt jwt.Service
|
jwt jwt.Service
|
||||||
user user.Service
|
user user.Service
|
||||||
|
cache cache.Service
|
||||||
|
|
||||||
provider *oidc.Provider
|
provider *oidc.Provider
|
||||||
conf oauth2.Config
|
conf oauth2.Config
|
||||||
|
Loading…
Reference in New Issue
Block a user