fix: cookie handling (#654)

* feat: set cookie prefix and max age

* cookie prefix on csrf cookie

* fix: check user agent cookie in login

* update oidc pkg

* cleanup
This commit is contained in:
Livio Amstutz 2020-08-31 08:49:35 +02:00 committed by GitHub
parent 1089193faf
commit c1c85e632b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 262 additions and 205 deletions

View File

@ -114,16 +114,13 @@ func startZitadel(configPaths []string) {
func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository) {
uis := ui.Create(conf.UI)
if *loginEnabled {
prefix := ""
if *localDevMode {
prefix = ui.LoginHandler
}
uis.RegisterHandler(ui.LoginHandler, login.Start(conf.UI.Login, authRepo, prefix).Handler())
login, prefix := login.Start(conf.UI.Login, authRepo, *localDevMode)
uis.RegisterHandler(prefix, login.Handler())
}
if *consoleEnabled {
consoleHandler, err := console.Start(conf.UI.Console)
consoleHandler, prefix, err := console.Start(conf.UI.Console)
logging.Log("API-AGD1f").OnError(err).Fatal("error starting console")
uis.RegisterHandler(ui.ConsoleHandler, consoleHandler)
uis.RegisterHandler(prefix, consoleHandler)
}
uis.Start(ctx)
}
@ -148,7 +145,7 @@ func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsReposit
apis.RegisterServer(ctx, auth.CreateServer(authRepo))
}
if *oidcEnabled {
op := oidc.NewProvider(ctx, conf.API.OIDC, authRepo)
op := oidc.NewProvider(ctx, conf.API.OIDC, authRepo, *localDevMode)
apis.RegisterHandler("/oauth/v2", op.HttpHandler())
}
apis.Start(ctx)

View File

@ -195,6 +195,7 @@ API:
UserAgentCookieConfig:
Name: caos.zitadel.useragent
Domain: $ZITADEL_COOKIE_DOMAIN
MaxAge: 8760h #365*24h (1 year)
Key:
EncryptionKeyID: $ZITADEL_COOKIE_KEY
Cache:
@ -230,6 +231,12 @@ UI:
Key:
EncryptionKeyID: $ZITADEL_CSRF_KEY
Development: $ZITADEL_CSRF_DEV
UserAgentCookieConfig:
Name: caos.zitadel.useragent
Domain: $ZITADEL_COOKIE_DOMAIN
MaxAge: 8760h #365*24h (1 year)
Key:
EncryptionKeyID: $ZITADEL_COOKIE_KEY
Cache:
MaxAge: $ZITADEL_CACHE_MAXAGE
SharedMaxAge: $ZITADEL_CACHE_SHARED_MAXAGE

2
go.mod
View File

@ -16,7 +16,7 @@ require (
github.com/aws/aws-sdk-go v1.33.13 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
github.com/caos/logging v0.0.2
github.com/caos/oidc v0.7.3
github.com/caos/oidc v0.7.4
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cockroachdb/cockroach-go/v2 v2.0.5
github.com/envoyproxy/protoc-gen-validate v0.4.0

4
go.sum
View File

@ -71,8 +71,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/oidc v0.7.3 h1:QWxzCJLv7tZ4HfZRrgM3qzlmc+sB7dD9+x2VIvyc7Co=
github.com/caos/oidc v0.7.3/go.mod h1:mnuSyFmv+WSuk2C/zps445xiMU9dW384/pV4WnIS8b0=
github.com/caos/oidc v0.7.4 h1:m98Cb+wL6aPVveNaJDgkFJGmEyyamOtO0AyOKLxXWXI=
github.com/caos/oidc v0.7.4/go.mod h1:mnuSyFmv+WSuk2C/zps445xiMU9dW384/pV4WnIS8b0=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

View File

@ -8,9 +8,15 @@ import (
"github.com/caos/zitadel/internal/errors"
)
const (
prefixSecure = "__Secure-"
prefixHost = "__Host-"
)
type CookieHandler struct {
securecookie *securecookie.SecureCookie
secureOnly bool
httpOnly bool
sameSite http.SameSite
path string
maxAge int
@ -20,6 +26,7 @@ type CookieHandler struct {
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
c := &CookieHandler{
secureOnly: true,
httpOnly: true,
sameSite: http.SameSiteLaxMode,
path: "/",
}
@ -44,6 +51,12 @@ func WithUnsecure() CookieHandlerOpt {
}
}
func WithNonHttpOnly() CookieHandlerOpt {
return func(c *CookieHandler) {
c.httpOnly = false
}
}
func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
return func(c *CookieHandler) {
c.sameSite = sameSite
@ -69,6 +82,16 @@ func WithDomain(domain string) CookieHandlerOpt {
}
}
func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
if !secureOnly {
return name
}
if domain != "" || path != "/" {
return prefixSecure + name
}
return prefixHost + name
}
func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, error) {
cookie, err := r.Cookie(name)
if err != nil {
@ -78,7 +101,7 @@ func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, er
}
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
cookie, err := r.Cookie(name)
cookie, err := r.Cookie(SetCookiePrefix(name, c.domain, c.path, c.secureOnly))
if err != nil {
return err
}
@ -110,12 +133,12 @@ func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
http.SetCookie(w, &http.Cookie{
Name: name,
Name: SetCookiePrefix(name, c.domain, c.path, c.secureOnly),
Value: value,
Domain: c.domain,
Path: c.path,
MaxAge: maxage,
HttpOnly: true,
HttpOnly: c.httpOnly,
Secure: c.secureOnly,
SameSite: c.sameSite,
})

View File

@ -41,13 +41,13 @@ var (
remoteAddr key
)
func CopyHeadersToContext(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func CopyHeadersToContext(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), httpHeaders, r.Header)
ctx = context.WithValue(ctx, remoteAddr, r.RemoteAddr)
r = r.WithContext(ctx)
h(w, r)
}
h.ServeHTTP(w, r)
})
}
func HeadersFromCtx(ctx context.Context) (http.Header, bool) {

View File

@ -9,10 +9,10 @@ import (
http_utils "github.com/caos/zitadel/internal/api/http"
)
type key int
type securityKey int
const (
nonceKey key = 0
nonceKey securityKey = 0
DefaultNonceLength = uint(32)
)

View File

@ -0,0 +1,103 @@
package middleware
import (
"context"
"net/http"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
)
type cookieKey int
var (
userAgentKey cookieKey = 0
)
func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
userAgentID, ok := ctx.Value(userAgentKey).(string)
return userAgentID, ok
}
type UserAgent struct {
ID string
}
type userAgentHandler struct {
cookieHandler *http_utils.CookieHandler
cookieName string
idGenerator id.Generator
nextHandler http.Handler
}
type UserAgentCookieConfig struct {
Name string
Domain string
Key *crypto.KeyConfig
MaxAge types.Duration
}
func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator, localDevMode bool) (func(http.Handler) http.Handler, error) {
key, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID)
if err != nil {
return nil, err
}
cookieKey := []byte(key)
opts := []http_utils.CookieHandlerOpt{
http_utils.WithEncryption(cookieKey, cookieKey),
http_utils.WithDomain(config.Domain),
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
}
if localDevMode {
opts = append(opts, http_utils.WithUnsecure())
}
return func(handler http.Handler) http.Handler {
return &userAgentHandler{
nextHandler: handler,
cookieName: config.Name,
cookieHandler: http_utils.NewCookieHandler(opts...),
idGenerator: idGenerator,
}
}, nil
}
func (ua *userAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
agent, err := ua.getUserAgent(r)
if err != nil {
agent, err = ua.newUserAgent()
}
if err == nil {
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
r = r.WithContext(ctx)
ua.setUserAgent(w, agent)
}
ua.nextHandler.ServeHTTP(w, r)
}
func (ua *userAgentHandler) newUserAgent() (*UserAgent, error) {
agentID, err := ua.idGenerator.Next()
if err != nil {
return nil, err
}
return &UserAgent{ID: agentID}, nil
}
func (ua *userAgentHandler) getUserAgent(r *http.Request) (*UserAgent, error) {
userAgent := new(UserAgent)
err := ua.cookieHandler.GetEncryptedCookieValue(r, ua.cookieName, userAgent)
if err != nil {
return nil, errors.ThrowPermissionDenied(err, "HTTP-YULqH4", "cannot read user agent cookie")
}
return userAgent, nil
}
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, agent *UserAgent) error {
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, agent)
if err != nil {
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
}
return nil
}

View File

@ -1,68 +0,0 @@
package http
import (
"net/http"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
)
type UserAgent struct {
ID string
}
type UserAgentHandler struct {
handler *CookieHandler
cookieName string
idGenerator id.Generator
}
type UserAgentCookieConfig struct {
Name string
Domain string
Key *crypto.KeyConfig
}
func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator) (*UserAgentHandler, error) {
key, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID)
if err != nil {
return nil, err
}
cookieKey := []byte(key)
handler := NewCookieHandler(
WithEncryption(cookieKey, cookieKey),
WithDomain(config.Domain),
WithUnsecure(),
)
return &UserAgentHandler{
cookieName: config.Name,
handler: handler,
idGenerator: idGenerator,
}, nil
}
func (ua *UserAgentHandler) NewUserAgent() (*UserAgent, error) {
agentID, err := ua.idGenerator.Next()
if err != nil {
return nil, err
}
return &UserAgent{ID: agentID}, nil
}
func (ua *UserAgentHandler) GetUserAgent(r *http.Request) (*UserAgent, error) {
userAgent := new(UserAgent)
err := ua.handler.GetEncryptedCookieValue(r, ua.cookieName, userAgent)
if err != nil {
return nil, errors.ThrowPermissionDenied(err, "HTTP-YULqH4", "cannot read user agent cookie")
}
return userAgent, nil
}
func (ua *UserAgentHandler) SetUserAgent(w http.ResponseWriter, agent *UserAgent) error {
err := ua.handler.SetEncryptedCookie(w, ua.cookieName, agent)
if err != nil {
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
}
return nil
}

View File

@ -9,13 +9,13 @@ import (
"github.com/caos/oidc/pkg/op"
"gopkg.in/square/go-jose.v2"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/errors"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
)
func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest, userID string) (op.AuthRequest, error) {
userAgentID, ok := UserAgentIDFromCtx(ctx)
userAgentID, ok := middleware.UserAgentIDFromCtx(ctx)
if !ok {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id")
}
@ -28,7 +28,11 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
}
func (o *OPStorage) AuthRequestByID(ctx context.Context, id string) (op.AuthRequest, error) {
resp, err := o.repo.AuthRequestByIDCheckLoggedIn(ctx, id)
userAgentID, ok := middleware.UserAgentIDFromCtx(ctx)
if !ok {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-D3g21", "no user agent id")
}
resp, err := o.repo.AuthRequestByIDCheckLoggedIn(ctx, id, userAgentID)
if err != nil {
return nil, err
}
@ -44,7 +48,11 @@ func (o *OPStorage) AuthRequestByCode(ctx context.Context, code string) (op.Auth
}
func (o *OPStorage) SaveAuthCode(ctx context.Context, id, code string) error {
return o.repo.SaveAuthCode(ctx, id, code)
userAgentID, ok := middleware.UserAgentIDFromCtx(ctx)
if !ok {
return errors.ThrowPreconditionFailed(nil, "OIDC-Dgus2", "no user agent id")
}
return o.repo.SaveAuthCode(ctx, id, code, userAgentID)
}
func (o *OPStorage) DeleteAuthRequest(ctx context.Context, id string) error {
@ -52,16 +60,13 @@ func (o *OPStorage) DeleteAuthRequest(ctx context.Context, id string) error {
}
func (o *OPStorage) CreateToken(ctx context.Context, authReq op.AuthRequest) (string, time.Time, error) {
req, err := o.repo.AuthRequestByID(ctx, authReq.GetID())
app, err := o.repo.ApplicationByClientID(ctx, authReq.GetClientID())
if err != nil {
return "", time.Time{}, err
}
app, err := o.repo.ApplicationByClientID(ctx, req.ApplicationID)
if err != nil {
return "", time.Time{}, err
}
grants, err := o.repo.UserGrantsByProjectAndUserID(app.ProjectID, req.UserID)
scopes := append(req.Request.(*model.AuthRequestOIDC).Scopes, grantsToScopes(grants)...)
grants, err := o.repo.UserGrantsByProjectAndUserID(app.ProjectID, authReq.GetSubject())
scopes := append(authReq.GetScopes(), grantsToScopes(grants)...)
req, _ := authReq.(*AuthRequest)
resp, err := o.repo.CreateToken(ctx, req.AgentID, req.ApplicationID, req.UserID, req.Audience, scopes, o.defaultAccessTokenLifetime) //PLANNED: lifetime from client
if err != nil {
return "", time.Time{}, err
@ -80,7 +85,7 @@ func grantsToScopes(grants []*grant_model.UserGrantView) []string {
}
func (o *OPStorage) TerminateSession(ctx context.Context, userID, clientID string) error {
userAgentID, ok := UserAgentIDFromCtx(ctx)
userAgentID, ok := middleware.UserAgentIDFromCtx(ctx)
if !ok {
return errors.ThrowPreconditionFailed(nil, "OIDC-fso7F", "no user agent id")
}

View File

@ -1,37 +0,0 @@
package oidc
import (
"context"
"net/http"
http_utils "github.com/caos/zitadel/internal/api/http"
)
type key int
var (
userAgentKey key
)
func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
userAgentID, ok := ctx.Value(userAgentKey).(string)
return userAgentID, ok
}
func UserAgentCookieHandler(cookieHandler *http_utils.UserAgentHandler, nextHandlerFunc func(http.HandlerFunc) http.HandlerFunc) func(http.HandlerFunc) http.HandlerFunc {
return func(handlerFunc http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ua, err := cookieHandler.GetUserAgent(r)
if err != nil {
ua, err = cookieHandler.NewUserAgent()
}
if err == nil {
ctx := context.WithValue(r.Context(), userAgentKey, ua.ID)
r = r.WithContext(ctx)
cookieHandler.SetUserAgent(w, ua)
}
handlerFunc(w, r)
nextHandlerFunc(handlerFunc)
}
}
}

View File

@ -2,7 +2,6 @@ package oidc
import (
"context"
"net/http"
"time"
"github.com/caos/logging"
@ -18,7 +17,7 @@ import (
type OPHandlerConfig struct {
OPConfig *op.Config
StorageConfig StorageConfig
UserAgentCookieConfig *http_utils.UserAgentCookieConfig
UserAgentCookieConfig *middleware.UserAgentCookieConfig
Cache *middleware.CacheConfig
Endpoints *EndpointConfig
}
@ -51,24 +50,18 @@ type OPStorage struct {
signingKeyAlgorithm string
}
func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Repository) op.OpenIDProvider {
cookieHandler, err := http_utils.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator)
func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Repository, localDevMode bool) op.OpenIDProvider {
cookieHandler, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode)
logging.Log("OIDC-sd4fd").OnError(err).Panic("cannot user agent handler")
nextHandler := func(handlerFunc http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.NoCacheInterceptor(http_utils.CopyHeadersToContext(handlerFunc))
}
}
config.OPConfig.CodeMethodS256 = true
provider, err := op.NewDefaultOP(
ctx,
config.OPConfig,
newStorage(config.StorageConfig, repo),
op.WithHttpInterceptor(
UserAgentCookieHandler(
cookieHandler,
nextHandler,
),
op.WithHttpInterceptors(
middleware.NoCacheInterceptor,
cookieHandler,
http_utils.CopyHeadersToContext,
),
op.WithCustomAuthEndpoint(op.NewEndpointWithURL(config.Endpoints.Auth.Path, config.Endpoints.Auth.URL)),
op.WithCustomTokenEndpoint(op.NewEndpointWithURL(config.Endpoints.Token.Path, config.Endpoints.Token.URL)),

View File

@ -8,13 +8,13 @@ import (
type AuthRequestRepository interface {
CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*model.AuthRequest, error)
AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
AuthRequestByIDCheckLoggedIn(ctx context.Context, id string) (*model.AuthRequest, error)
AuthRequestByID(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error)
AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error)
AuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error)
SaveAuthCode(ctx context.Context, id, code string) error
SaveAuthCode(ctx context.Context, id, code, userAgentID string) error
DeleteAuthRequest(ctx context.Context, id string) error
CheckLoginName(ctx context.Context, id, loginName string) error
SelectUser(ctx context.Context, id, userID string) error
VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error
VerifyMfaOTP(ctx context.Context, agentID, authRequestID string, code string, info *model.BrowserInfo) error
CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error
SelectUser(ctx context.Context, id, userID, userAgentID string) error
VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error
VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error
}

View File

@ -83,16 +83,16 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
return request, nil
}
func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error) {
return repo.getAuthRequest(ctx, id, false)
func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) {
return repo.getAuthRequestNextSteps(ctx, id, userAgentID, false)
}
func (repo *AuthRequestRepo) AuthRequestByIDCheckLoggedIn(ctx context.Context, id string) (*model.AuthRequest, error) {
return repo.getAuthRequest(ctx, id, true)
func (repo *AuthRequestRepo) AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) {
return repo.getAuthRequestNextSteps(ctx, id, userAgentID, true)
}
func (repo *AuthRequestRepo) SaveAuthCode(ctx context.Context, id, code string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
func (repo *AuthRequestRepo) SaveAuthCode(ctx context.Context, id, code, userAgentID string) error {
request, err := repo.getAuthRequest(ctx, id, userAgentID)
if err != nil {
return err
}
@ -117,8 +117,8 @@ func (repo *AuthRequestRepo) DeleteAuthRequest(ctx context.Context, id string) e
return repo.AuthRequests.DeleteAuthRequest(ctx, id)
}
func (repo *AuthRequestRepo) CheckLoginName(ctx context.Context, id, loginName string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
func (repo *AuthRequestRepo) CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error {
request, err := repo.getAuthRequest(ctx, id, userAgentID)
if err != nil {
return err
}
@ -129,8 +129,8 @@ func (repo *AuthRequestRepo) CheckLoginName(ctx context.Context, id, loginName s
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAgentID string) error {
request, err := repo.getAuthRequest(ctx, id, userAgentID)
if err != nil {
return err
}
@ -142,8 +142,8 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error {
request, err := repo.getAuthRequest(ctx, id, userAgentID)
if err != nil {
return err
}
@ -153,8 +153,8 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, pas
return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info))
}
func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, userID string, code string, info *model.BrowserInfo) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, authRequestID)
func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, userID, code, userAgentID string, info *model.BrowserInfo) error {
request, err := repo.getAuthRequest(ctx, authRequestID, userAgentID)
if err != nil {
return err
}
@ -164,8 +164,8 @@ func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, us
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
}
func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id string, checkLoggedIn bool) (*model.AuthRequest, error) {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, userAgentID string, checkLoggedIn bool) (*model.AuthRequest, error) {
request, err := repo.getAuthRequest(ctx, id, userAgentID)
if err != nil {
return nil, err
}
@ -177,6 +177,17 @@ func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id string, chec
return request, nil
}
func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return nil, err
}
if request.AgentID != userAgentID {
return nil, errors.ThrowPermissionDenied(nil, "EVENT-adk13", "Errors.AuthRequest.UserAgentNotCorresponding")
}
return request, nil
}
func (repo *AuthRequestRepo) checkLoginName(request *model.AuthRequest, loginName string) error {
user, err := repo.View.UserByLoginName(loginName)
if err != nil {

View File

@ -28,6 +28,7 @@ type spaHandler struct {
const (
envRequestPath = "/assets/environment.json"
envDefaultDir = "/console/"
handlerPrefix = "/console"
)
var (
@ -50,10 +51,10 @@ func (i *spaHandler) Open(name string) (http.File, error) {
return i.fileSystem.Open("/index.html")
}
func Start(config Config) (http.Handler, error) {
func Start(config Config) (http.Handler, string, error) {
statikFS, err := fs.NewWithNamespace("console")
if err != nil {
return nil, err
return nil, "", err
}
envDir := envDefaultDir
if config.EnvOverwriteDir != "" {
@ -69,7 +70,7 @@ func Start(config Config) (http.Handler, error) {
handler := &http.ServeMux{}
handler.Handle("/", cache(security(http.FileServer(&spaHandler{statikFS}))))
handler.Handle(envRequestPath, cache(security(http.StripPrefix("/assets", http.FileServer(http.Dir(envDir))))))
return handler, nil
return handler, handlerPrefix, nil
}
func csp(zitadelDomain string) *middleware.CSP {

View File

@ -3,6 +3,7 @@ package handler
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
)
@ -15,7 +16,8 @@ func (l *Login) getAuthRequest(r *http.Request) (*model.AuthRequest, error) {
if authRequestID == "" {
return nil, nil
}
return l.authRepo.AuthRequestByID(r.Context(), authRequestID)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
return l.authRepo.AuthRequestByID(r.Context(), authRequestID, userAgentID)
}
func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*model.AuthRequest, error) {

View File

@ -11,11 +11,14 @@ import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/http/middleware"
auth_repository "github.com/caos/zitadel/internal/auth/repository"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/form"
"github.com/caos/zitadel/internal/id"
_ "github.com/caos/zitadel/internal/ui/login/statik"
)
@ -30,12 +33,13 @@ type Login struct {
}
type Config struct {
OidcAuthCallbackURL string
ZitadelURL string
LanguageCookieName string
DefaultLanguage language.Tag
CSRF CSRF
Cache middleware.CacheConfig
OidcAuthCallbackURL string
ZitadelURL string
LanguageCookieName string
DefaultLanguage language.Tag
CSRF CSRF
UserAgentCookieConfig *middleware.UserAgentCookieConfig
Cache middleware.CacheConfig
}
type CSRF struct {
@ -45,15 +49,20 @@ type CSRF struct {
}
const (
login = "LOGIN"
login = "LOGIN"
handlerPrefix = "/login"
)
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, prefix string) *Login {
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, localDevMode bool) (*Login, string) {
login := &Login{
oidcAuthCallbackURL: config.OidcAuthCallbackURL,
zitadelURL: config.ZitadelURL,
authRepo: authRepo,
}
prefix := ""
if localDevMode {
prefix = handlerPrefix
}
statikFS, err := fs.NewWithNamespace("login")
logging.Log("CONFI-Ga21f").OnError(err).Panic("unable to create filesystem")
@ -62,10 +71,12 @@ func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, prefix str
cache, err := middleware.DefaultCacheInterceptor(EndpointResources, config.Cache.MaxAge.Duration, config.Cache.SharedMaxAge.Duration)
logging.Log("CONFI-BHq2a").OnError(err).Panic("unable to create cacheInterceptor")
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
login.router = CreateRouter(login, statikFS, csrf, cache, security)
userAgentCookie, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode)
logging.Log("CONFI-Dvwf2").OnError(err).Panic("unable to create userAgentInterceptor")
login.router = CreateRouter(login, statikFS, csrf, cache, security, userAgentCookie)
login.renderer = CreateRenderer(prefix, statikFS, config.LanguageCookieName, config.DefaultLanguage)
login.parser = form.NewParser()
return login
return login, prefix
}
func csp() *middleware.CSP {
@ -81,10 +92,11 @@ func csrfInterceptor(config CSRF, errorHandler http.Handler) (func(http.Handler)
if err != nil {
return nil, err
}
path := "/"
return csrf.Protect([]byte(csrfKey),
csrf.Secure(!config.Development),
csrf.CookieName(config.CookieName),
csrf.Path("/"),
csrf.CookieName(http_utils.SetCookiePrefix(config.CookieName, "", path, !config.Development)),
csrf.Path(path),
csrf.ErrorHandler(errorHandler),
), nil
}

View File

@ -3,6 +3,7 @@ package handler
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
)
@ -48,7 +49,8 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
l.handleRegister(w, r)
return
}
err = l.authRepo.CheckLoginName(r.Context(), authReq.ID, data.LoginName)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.CheckLoginName(r.Context(), authReq.ID, data.LoginName, userAgentID)
if err != nil {
l.renderLogin(w, r, authReq, err)
return

View File

@ -3,6 +3,7 @@ package handler
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
)
@ -23,7 +24,8 @@ func (l *Login) handleMfaVerify(w http.ResponseWriter, r *http.Request) {
return
}
if data.MfaType == model.MfaTypeOTP {
err = l.authRepo.VerifyMfaOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Code, model.BrowserInfoFromRequest(r))
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.VerifyMfaOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Code, userAgentID, model.BrowserInfoFromRequest(r))
}
if err != nil {
l.renderError(w, r, authReq, err)

View File

@ -3,6 +3,7 @@ package handler
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
)
@ -30,7 +31,8 @@ func (l *Login) handlePasswordCheck(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, authReq, err)
return
}
err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Password, model.BrowserInfoFromRequest(r))
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Password, userAgentID, model.BrowserInfoFromRequest(r))
if err != nil {
l.renderPassword(w, r, authReq, err)
return

View File

@ -7,16 +7,15 @@ import (
"net/http"
"path"
"github.com/caos/logging"
"github.com/gorilla/csrf"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/http/middleware"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/renderer"
"github.com/caos/logging"
"golang.org/x/text/language"
)
const (
@ -135,7 +134,8 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
}
func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
if err != nil {
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-sio0W", "could not get authreq"))
}
@ -219,7 +219,7 @@ func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title s
ThemeMode: l.getThemeMode(r),
AuthReqID: getRequestID(authReq, r),
CSRF: csrf.TemplateField(r),
Nonce: middleware.GetNonce(r),
Nonce: http_mw.GetNonce(r),
}
}

View File

@ -3,6 +3,7 @@ package handler
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
)
@ -33,7 +34,8 @@ func (l *Login) handleSelectUser(w http.ResponseWriter, r *http.Request) {
l.renderLogin(w, r, authSession, nil)
return
}
err = l.authRepo.SelectUser(r.Context(), authSession.ID, data.UserID)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.SelectUser(r.Context(), authSession.ID, data.UserID, userAgentID)
if err != nil {
l.renderError(w, r, authSession, err)
return

View File

@ -9,6 +9,6 @@ type Config struct {
Handler handler.Config
}
func Start(config Config, authRepo *eventsourcing.EsRepository, pathPrefix string) *handler.Login {
return handler.CreateLogin(config.Handler, authRepo, pathPrefix)
func Start(config Config, authRepo *eventsourcing.EsRepository, localDevMode bool) (*handler.Login, string) {
return handler.CreateLogin(config.Handler, authRepo, localDevMode)
}

View File

@ -157,6 +157,7 @@ Errors:
Internal: Es ist ein interner Fehler aufgetreten
AuthRequest:
NotFound: AuthRequest konnte nicht gefunden werden
UserAgentNotCorresponding: User Agent stimmt nicht überein
User:
NotFound: Benutzer konnte nicht gefunden werden
NotMatchingUserID: User stimm nicht mit User in Auth Request überein

View File

@ -159,6 +159,7 @@ Errors:
Internal: An internal error occured
AuthRequest:
NotFound: Could not find authrequest
UserAgentNotCorresponding: User Agent does not correspond
User:
NotFound: User could not be found
NotMatchingUserID: User and user in authrequest don't match

View File

@ -10,9 +10,7 @@ import (
)
const (
LoginHandler = "/login"
ConsoleHandler = "/console"
uiname = "ui"
uiname = "ui"
)
type Config struct {