mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 13:01:38 +00:00
feat: dynamic issuer (#3481)
* feat: dynamic issuer * dynamic domain handling * key rotation durations * feat: dynamic issuer * make webauthn displayname dynamic
This commit is contained in:
@@ -3,7 +3,7 @@ package actions
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
)
|
||||
|
||||
type Context map[string]interface{}
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
@@ -20,7 +21,6 @@ type CookieHandler struct {
|
||||
sameSite http.SameSite
|
||||
path string
|
||||
maxAge int
|
||||
domain string
|
||||
}
|
||||
|
||||
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
|
||||
@@ -76,12 +76,6 @@ func WithMaxAge(maxAge int) CookieHandlerOpt {
|
||||
}
|
||||
}
|
||||
|
||||
func WithDomain(domain string) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.domain = domain
|
||||
}
|
||||
}
|
||||
|
||||
func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
|
||||
if !secureOnly {
|
||||
return name
|
||||
@@ -101,7 +95,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(SetCookiePrefix(name, c.domain, c.path, c.secureOnly))
|
||||
cookie, err := r.Cookie(SetCookiePrefix(name, r.Host, c.path, c.secureOnly))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -111,11 +105,11 @@ func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, va
|
||||
return c.securecookie.Decode(name, cookie.Value, value)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) {
|
||||
c.httpSet(w, name, value, c.maxAge)
|
||||
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, domain, value string) {
|
||||
c.httpSet(w, name, domain, value, c.maxAge)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, value interface{}) error {
|
||||
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name, domain string, value interface{}) error {
|
||||
if c.securecookie == nil {
|
||||
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
|
||||
}
|
||||
@@ -123,19 +117,19 @@ func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, v
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.httpSet(w, name, encoded, c.maxAge)
|
||||
c.httpSet(w, name, domain, encoded, c.maxAge)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
|
||||
c.httpSet(w, name, "", -1)
|
||||
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, r *http.Request, name string) {
|
||||
c.httpSet(w, name, r.Host, "", -1)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
|
||||
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, domain, value string, maxage int) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: SetCookiePrefix(name, c.domain, c.path, c.secureOnly),
|
||||
Name: SetCookiePrefix(name, domain, c.path, c.secureOnly),
|
||||
Value: value,
|
||||
Domain: c.domain,
|
||||
Domain: strings.Split(domain, ":")[0],
|
||||
Path: c.path,
|
||||
MaxAge: maxage,
|
||||
HttpOnly: c.httpOnly,
|
||||
|
@@ -34,7 +34,7 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (csp *CSP) Value(nonce string) string {
|
||||
func (csp *CSP) Value(nonce string, host string) string {
|
||||
valuesMap := csp.asMap()
|
||||
|
||||
values := make([]string, 0, len(valuesMap))
|
||||
@@ -43,7 +43,7 @@ func (csp *CSP) Value(nonce string) string {
|
||||
continue
|
||||
}
|
||||
|
||||
values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce)))
|
||||
values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce, host)))
|
||||
}
|
||||
|
||||
return strings.Join(values, ";")
|
||||
@@ -99,24 +99,33 @@ func (srcOpts CSPSourceOptions) AddHost(h ...string) CSPSourceOptions {
|
||||
return append(srcOpts, h...)
|
||||
}
|
||||
|
||||
func (srcOpts CSPSourceOptions) AddOwnHost() CSPSourceOptions {
|
||||
return append(srcOpts, placeHolderHost)
|
||||
}
|
||||
|
||||
func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions {
|
||||
return srcOpts.add(s, "%v:")
|
||||
}
|
||||
|
||||
func (srcOpts CSPSourceOptions) AddNonce() CSPSourceOptions {
|
||||
return append(srcOpts, "'nonce-%v'")
|
||||
return append(srcOpts, fmt.Sprintf("'nonce-%s'", placeHolderNonce))
|
||||
}
|
||||
|
||||
const (
|
||||
placeHolderNonce = "{{nonce}}"
|
||||
placeHolderHost = "{{host}}"
|
||||
)
|
||||
|
||||
func (srcOpts CSPSourceOptions) AddHash(alg, b64v string) CSPSourceOptions {
|
||||
return append(srcOpts, fmt.Sprintf("'%v-%v'", alg, b64v))
|
||||
}
|
||||
|
||||
func (srcOpts CSPSourceOptions) String(nonce string) string {
|
||||
func (srcOpts CSPSourceOptions) String(nonce, host string) string {
|
||||
value := strings.Join(srcOpts, " ")
|
||||
if !strings.Contains(value, "%v") {
|
||||
if !strings.Contains(value, placeHolderNonce) && !strings.Contains(value, placeHolderHost) {
|
||||
return value
|
||||
}
|
||||
return fmt.Sprintf(value, nonce)
|
||||
return strings.ReplaceAll(strings.ReplaceAll(value, placeHolderHost, host), placeHolderNonce, nonce)
|
||||
}
|
||||
|
||||
func (srcOpts CSPSourceOptions) add(values []string, format string) CSPSourceOptions {
|
||||
|
@@ -63,7 +63,7 @@ func (h *headers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r = saveContext(r, nonceKey, nonce)
|
||||
}
|
||||
headers := w.Header()
|
||||
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce))
|
||||
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce, r.Host))
|
||||
headers.Set(http_utils.XXSSProtection, "1; mode=block")
|
||||
headers.Set(http_utils.StrictTransportSecurity, "max-age=31536000; includeSubDomains")
|
||||
headers.Set(http_utils.XFrameOptions, "DENY")
|
||||
|
@@ -21,10 +21,6 @@ func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
|
||||
return userAgentID, ok
|
||||
}
|
||||
|
||||
func InstanceIDFromCtx(ctx context.Context) string {
|
||||
return "" //TODO: implement
|
||||
}
|
||||
|
||||
type UserAgent struct {
|
||||
ID string
|
||||
}
|
||||
@@ -41,10 +37,9 @@ type UserAgentCookieConfig struct {
|
||||
MaxAge time.Duration
|
||||
}
|
||||
|
||||
func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) {
|
||||
func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) {
|
||||
opts := []http_utils.CookieHandlerOpt{
|
||||
http_utils.WithEncryption(cookieKey, cookieKey),
|
||||
http_utils.WithDomain(domain),
|
||||
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
|
||||
}
|
||||
if !externalSecure {
|
||||
@@ -68,7 +63,7 @@ func (ua *userAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err == nil {
|
||||
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
|
||||
r = r.WithContext(ctx)
|
||||
ua.setUserAgent(w, agent)
|
||||
ua.setUserAgent(w, r.Host, agent)
|
||||
}
|
||||
ua.nextHandler.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -90,8 +85,8 @@ func (ua *userAgentHandler) getUserAgent(r *http.Request) (*UserAgent, error) {
|
||||
return userAgent, nil
|
||||
}
|
||||
|
||||
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, agent *UserAgent) error {
|
||||
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, agent)
|
||||
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, host string, agent *UserAgent) error {
|
||||
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, host, agent)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
|
||||
}
|
||||
|
@@ -32,9 +32,13 @@ func IsOrigin(rawOrigin string) bool {
|
||||
}
|
||||
|
||||
func BuildHTTP(hostname string, externalPort uint16, secure bool) string {
|
||||
return BuildOrigin(fmt.Sprintf("%s:%d", hostname, externalPort), secure)
|
||||
}
|
||||
|
||||
func BuildOrigin(host string, secure bool) string {
|
||||
schema := "https"
|
||||
if !secure {
|
||||
schema = "http"
|
||||
}
|
||||
return fmt.Sprintf("%s://%s:%d", schema, hostname, externalPort)
|
||||
return fmt.Sprintf("%s://%s", schema, host)
|
||||
}
|
||||
|
@@ -6,8 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||
|
@@ -6,8 +6,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
|
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
@@ -43,7 +43,7 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "OIDC-mPxqP", "Errors.Internal")
|
||||
}
|
||||
projectRoles, err := o.query.SearchProjectRoles(context.TODO(), &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||
projectRoles, err := o.query.SearchProjectRoles(ctx, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -4,8 +4,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
@@ -20,99 +21,121 @@ import (
|
||||
const (
|
||||
locksTable = "projections.locks"
|
||||
signingKey = "signing_key"
|
||||
oidcUser = "OIDC"
|
||||
|
||||
retryBackoff = 500 * time.Millisecond
|
||||
retryCount = 3
|
||||
lockDuration = retryCount * retryBackoff * 5
|
||||
gracefulPeriod = 10 * time.Minute
|
||||
)
|
||||
|
||||
func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err error) {
|
||||
//SigningKey wraps the query.PrivateKey to implement the op.SigningKey interface
|
||||
type SigningKey struct {
|
||||
algorithm jose.SignatureAlgorithm
|
||||
id string
|
||||
key interface{}
|
||||
}
|
||||
|
||||
func (s *SigningKey) SignatureAlgorithm() jose.SignatureAlgorithm {
|
||||
return s.algorithm
|
||||
}
|
||||
|
||||
func (s *SigningKey) Key() interface{} {
|
||||
return s.key
|
||||
}
|
||||
|
||||
func (s *SigningKey) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
//PublicKey wraps the query.PublicKey to implement the op.Key interface
|
||||
type PublicKey struct {
|
||||
key query.PublicKey
|
||||
}
|
||||
|
||||
func (s *PublicKey) Algorithm() jose.SignatureAlgorithm {
|
||||
return jose.SignatureAlgorithm(s.key.Algorithm())
|
||||
}
|
||||
|
||||
func (s *PublicKey) Use() string {
|
||||
return s.key.Use().String()
|
||||
}
|
||||
|
||||
func (s *PublicKey) Key() interface{} {
|
||||
return s.key.Key()
|
||||
}
|
||||
|
||||
func (s *PublicKey) ID() string {
|
||||
return s.key.ID()
|
||||
}
|
||||
|
||||
//KeySet implements the op.Storage interface
|
||||
func (o *OPStorage) KeySet(ctx context.Context) (keys []op.Key, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
keys, err := o.query.ActivePublicKeys(ctx, time.Now())
|
||||
err = retry(func() error {
|
||||
publicKeys, err := o.query.ActivePublicKeys(ctx, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys = make([]op.Key, len(publicKeys.Keys))
|
||||
for i, key := range publicKeys.Keys {
|
||||
keys[i] = &PublicKey{key}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return keys, err
|
||||
}
|
||||
|
||||
//SignatureAlgorithms implements the op.Storage interface
|
||||
func (o *OPStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||
key, err := o.SigningKey(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webKeys := make([]jose.JSONWebKey, len(keys.Keys))
|
||||
for i, key := range keys.Keys {
|
||||
webKeys[i] = jose.JSONWebKey{
|
||||
KeyID: key.ID(),
|
||||
Algorithm: key.Algorithm(),
|
||||
Use: key.Use().String(),
|
||||
Key: key.Key(),
|
||||
}
|
||||
}
|
||||
return &jose.JSONWebKeySet{Keys: webKeys}, nil
|
||||
return []jose.SignatureAlgorithm{key.SignatureAlgorithm()}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) {
|
||||
renewTimer := time.NewTimer(0)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-o.keyChan:
|
||||
if !renewTimer.Stop() {
|
||||
<-renewTimer.C
|
||||
}
|
||||
checkAfter := o.resetTimer(renewTimer, true)
|
||||
logging.Infof("requested next signing key check in %s", checkAfter)
|
||||
case <-renewTimer.C:
|
||||
o.getSigningKey(ctx, renewTimer, keyCh)
|
||||
}
|
||||
//SigningKey implements the op.Storage interface
|
||||
func (o *OPStorage) SigningKey(ctx context.Context) (key op.SigningKey, err error) {
|
||||
err = retry(func() error {
|
||||
key, err = o.getSigningKey(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}()
|
||||
if key == nil {
|
||||
return errors.ThrowInternal(nil, "test", "test")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return key, err
|
||||
}
|
||||
|
||||
func (o *OPStorage) getSigningKey(ctx context.Context, renewTimer *time.Timer, keyCh chan<- jose.SigningKey) {
|
||||
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(o.signingKeyGracefulPeriod))
|
||||
func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(gracefulPeriod))
|
||||
if err != nil {
|
||||
checkAfter := o.resetTimer(renewTimer, true)
|
||||
logging.Infof("next signing key check in %s", checkAfter)
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
if len(keys.Keys) == 0 {
|
||||
var sequence uint64
|
||||
if keys.LatestSequence != nil {
|
||||
sequence = keys.LatestSequence.Sequence
|
||||
}
|
||||
o.refreshSigningKey(ctx, keyCh, o.signingKeyAlgorithm, sequence)
|
||||
checkAfter := o.resetTimer(renewTimer, true)
|
||||
logging.Infof("next signing key check in %s", checkAfter)
|
||||
return
|
||||
if len(keys.Keys) > 0 {
|
||||
return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
|
||||
}
|
||||
err = o.exchangeSigningKey(selectSigningKey(keys.Keys), keyCh)
|
||||
logging.OnError(err).Error("could not exchange signing key")
|
||||
checkAfter := o.resetTimer(renewTimer, err != nil)
|
||||
logging.Infof("next signing key check in %s", checkAfter)
|
||||
var sequence uint64
|
||||
if keys.LatestSequence != nil {
|
||||
sequence = keys.LatestSequence.Sequence
|
||||
}
|
||||
return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, sequence)
|
||||
}
|
||||
|
||||
func (o *OPStorage) resetTimer(timer *time.Timer, shortRefresh bool) (nextCheck time.Duration) {
|
||||
nextCheck = o.signingKeyRotationCheck
|
||||
defer func() { timer.Reset(nextCheck) }()
|
||||
if shortRefresh || o.currentKey == nil {
|
||||
return nextCheck
|
||||
}
|
||||
maxLifetime := time.Until(o.currentKey.Expiry())
|
||||
if maxLifetime < o.signingKeyGracefulPeriod+2*o.signingKeyRotationCheck {
|
||||
return nextCheck
|
||||
}
|
||||
return maxLifetime - o.signingKeyGracefulPeriod - o.signingKeyRotationCheck
|
||||
}
|
||||
|
||||
func (o *OPStorage) refreshSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, algorithm string, sequence uint64) {
|
||||
if o.currentKey != nil && o.currentKey.Expiry().Before(time.Now().UTC()) {
|
||||
logging.Info("unset current signing key")
|
||||
keyCh <- jose.SigningKey{}
|
||||
}
|
||||
func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, sequence uint64) error {
|
||||
ok, err := o.ensureIsLatestKey(ctx, sequence)
|
||||
if err != nil {
|
||||
logging.New().WithError(err).Error("could not ensure latest key")
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
logging.Warn("view not up to date, retrying later")
|
||||
return
|
||||
if err != nil || !ok {
|
||||
return errors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
|
||||
}
|
||||
err = o.lockAndGenerateSigningKeyPair(ctx, algorithm)
|
||||
logging.OnError(err).Warn("could not create signing key")
|
||||
if err != nil {
|
||||
return errors.ThrowInternal(err, "OIDC-ADh31", "could not create signing key")
|
||||
}
|
||||
return errors.ThrowInternal(nil, "OIDC-Df1bh", "")
|
||||
}
|
||||
|
||||
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (bool, error) {
|
||||
@@ -123,29 +146,20 @@ func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (boo
|
||||
return sequence == maxSequence, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) exchangeSigningKey(key query.PrivateKey, keyCh chan<- jose.SigningKey) (err error) {
|
||||
if o.currentKey != nil && o.currentKey.ID() == key.ID() {
|
||||
logging.Info("no new signing key")
|
||||
return nil
|
||||
}
|
||||
func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) {
|
||||
keyData, err := crypto.Decrypt(key.Key(), o.encAlg)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := crypto.BytesToPrivateKey(keyData)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
keyCh <- jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(key.Algorithm()),
|
||||
Key: jose.JSONWebKey{
|
||||
KeyID: key.ID(),
|
||||
Key: privateKey,
|
||||
},
|
||||
}
|
||||
o.currentKey = key
|
||||
logging.WithFields("keyID", key.ID()).Info("exchanged signing key")
|
||||
return nil
|
||||
return &SigningKey{
|
||||
algorithm: jose.SignatureAlgorithm(key.Algorithm()),
|
||||
key: privateKey,
|
||||
id: key.ID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||
@@ -154,7 +168,7 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
errs := o.locker.Lock(ctx, o.signingKeyRotationCheck*2, authz.GetInstance(ctx).InstanceID())
|
||||
errs := o.locker.Lock(ctx, lockDuration, authz.GetInstance(ctx).InstanceID())
|
||||
err, ok := <-errs
|
||||
if err != nil || !ok {
|
||||
if errors.IsErrorAlreadyExists(err) {
|
||||
@@ -164,13 +178,13 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
|
||||
return err
|
||||
}
|
||||
|
||||
return o.command.GenerateSigningKeyPair(ctx, algorithm)
|
||||
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
|
||||
}
|
||||
|
||||
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
|
||||
return o.eventstore.LatestSequence(ctx,
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||
ResourceOwner("system"). //TODO: change with multi issuer
|
||||
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
|
||||
AddQuery().
|
||||
AggregateTypes(keypair.AggregateType).
|
||||
Builder(),
|
||||
@@ -180,3 +194,18 @@ func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
|
||||
func selectSigningKey(keys []query.PrivateKey) query.PrivateKey {
|
||||
return keys[len(keys)-1]
|
||||
}
|
||||
|
||||
func setOIDCCtx(ctx context.Context) context.Context {
|
||||
return authz.SetCtxData(ctx, authz.CtxData{UserID: oidcUser, OrgID: authz.GetInstance(ctx).InstanceID()})
|
||||
}
|
||||
|
||||
func retry(retryable func() error) (err error) {
|
||||
for i := 0; i < retryCount; i++ {
|
||||
time.Sleep(retryBackoff)
|
||||
err = retryable()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/api/ui/login"
|
||||
"github.com/caos/zitadel/internal/auth/repository"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
@@ -74,26 +73,23 @@ type OPStorage struct {
|
||||
defaultRefreshTokenIdleExpiration time.Duration
|
||||
defaultRefreshTokenExpiration time.Duration
|
||||
encAlg crypto.EncryptionAlgorithm
|
||||
keyChan <-chan interface{}
|
||||
currentKey query.PrivateKey
|
||||
signingKeyRotationCheck time.Duration
|
||||
signingKeyGracefulPeriod time.Duration
|
||||
locker crdb.Locker
|
||||
assetAPIPrefix string
|
||||
}
|
||||
|
||||
func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
|
||||
opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey)
|
||||
func NewProvider(ctx context.Context, config Config, defaultLogoutRedirectURI string, externalSecure bool, command *command.Commands, query *query.Queries, repo repository.Repository, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
|
||||
opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
|
||||
}
|
||||
storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan)
|
||||
options, err := createOptions(config, userAgentCookie, instanceHandler)
|
||||
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections)
|
||||
options, err := createOptions(config, externalSecure, userAgentCookie, instanceHandler)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
|
||||
}
|
||||
provider, err := op.NewOpenIDProvider(
|
||||
provider, err := op.NewDynamicOpenIDProvider(
|
||||
ctx,
|
||||
HandlerPrefix,
|
||||
opConfig,
|
||||
storage,
|
||||
options...,
|
||||
@@ -104,17 +100,12 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func Issuer(domain string, port uint16, externalSecure bool) string {
|
||||
return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix
|
||||
}
|
||||
|
||||
func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
|
||||
func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
|
||||
supportedLanguages, err := getSupportedLanguages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opConfig := &op.Config{
|
||||
Issuer: issuer,
|
||||
DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
|
||||
CodeMethodS256: config.CodeMethodS256,
|
||||
AuthMethodPost: config.AuthMethodPost,
|
||||
@@ -130,21 +121,26 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryp
|
||||
return opConfig, nil
|
||||
}
|
||||
|
||||
func createOptions(config Config, userAgentCookie, instanceHandler func(http.Handler) http.Handler) ([]op.Option, error) {
|
||||
func createOptions(config Config, externalSecure bool, userAgentCookie, instanceHandler func(http.Handler) http.Handler) ([]op.Option, error) {
|
||||
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
|
||||
interceptor := op.WithHttpInterceptors(
|
||||
middleware.MetricsHandler(metricTypes),
|
||||
middleware.TelemetryHandler(),
|
||||
middleware.NoCacheInterceptor,
|
||||
instanceHandler,
|
||||
userAgentCookie,
|
||||
http_utils.CopyHeadersToContext,
|
||||
)
|
||||
endpoints := customEndpoints(config.CustomEndpoints)
|
||||
if len(endpoints) == 0 {
|
||||
return []op.Option{interceptor}, nil
|
||||
options := []op.Option{
|
||||
op.WithHttpInterceptors(
|
||||
middleware.MetricsHandler(metricTypes),
|
||||
middleware.TelemetryHandler(),
|
||||
middleware.NoCacheInterceptor,
|
||||
instanceHandler,
|
||||
userAgentCookie,
|
||||
http_utils.CopyHeadersToContext,
|
||||
),
|
||||
}
|
||||
return append(endpoints, interceptor), nil
|
||||
if !externalSecure {
|
||||
options = append(options, op.WithAllowInsecure())
|
||||
}
|
||||
endpoints := customEndpoints(config.CustomEndpoints)
|
||||
if len(endpoints) != 0 {
|
||||
options = append(options, endpoints...)
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
|
||||
@@ -176,7 +172,7 @@ func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
|
||||
return options
|
||||
}
|
||||
|
||||
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}) *OPStorage {
|
||||
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB) *OPStorage {
|
||||
return &OPStorage{
|
||||
repo: repo,
|
||||
command: command,
|
||||
@@ -189,10 +185,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries,
|
||||
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
|
||||
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
|
||||
encAlg: encAlg,
|
||||
signingKeyGracefulPeriod: keyConfig.SigningKeyGracefulPeriod,
|
||||
signingKeyRotationCheck: keyConfig.SigningKeyRotationCheck,
|
||||
locker: crdb.NewLocker(projections, locksTable, signingKey),
|
||||
keyChan: keyChan,
|
||||
assetAPIPrefix: assets.HandlerPrefix,
|
||||
}
|
||||
}
|
||||
|
@@ -8,13 +8,13 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/v2/pkg/op"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (i *spaHandler) Open(name string) (http.File, error) {
|
||||
return i.fileSystem.Open("/index.html")
|
||||
}
|
||||
|
||||
func Start(config Config, domain, url, issuer string, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) {
|
||||
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) {
|
||||
fSys, err := fs.Sub(static, "static")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -70,7 +70,7 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
|
||||
config.LongCache.MaxAge,
|
||||
config.LongCache.SharedMaxAge,
|
||||
)
|
||||
security := middleware.SecurityHeaders(csp(domain), nil)
|
||||
security := middleware.SecurityHeaders(csp(), nil)
|
||||
|
||||
handler := &http.ServeMux{}
|
||||
handler.Handle("/", cache(security(http.FileServer(&spaHandler{http.FS(fSys)}))))
|
||||
@@ -80,7 +80,8 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
|
||||
http.Error(w, "empty instanceID", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
environmentJSON, err := createEnvironmentJSON(url, issuer, instance.ConsoleClientID())
|
||||
url := http_util.BuildOrigin(r.Host, externalSecure)
|
||||
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID())
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -91,15 +92,12 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func csp(zitadelDomain string) *middleware.CSP {
|
||||
if !strings.HasPrefix(zitadelDomain, "*.") {
|
||||
zitadelDomain = "*." + zitadelDomain
|
||||
}
|
||||
func csp() *middleware.CSP {
|
||||
csp := middleware.DefaultSCP
|
||||
csp.StyleSrc = csp.StyleSrc.AddInline()
|
||||
csp.ScriptSrc = csp.ScriptSrc.AddEval()
|
||||
csp.ConnectSrc = csp.ConnectSrc.AddHost(zitadelDomain)
|
||||
csp.ImgSrc = csp.ImgSrc.AddHost(zitadelDomain).AddScheme("blob")
|
||||
csp.ConnectSrc = csp.ConnectSrc.AddOwnHost()
|
||||
csp.ImgSrc = csp.ImgSrc.AddOwnHost().AddScheme("blob")
|
||||
return &csp
|
||||
}
|
||||
|
||||
|
@@ -3,10 +3,10 @@ package login
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
|
||||
"github.com/caos/zitadel/internal/actions"
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/client/rp"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||
|
@@ -4,8 +4,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/client/rp"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/client/rp"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
|
@@ -25,18 +25,17 @@ import (
|
||||
)
|
||||
|
||||
type Login struct {
|
||||
endpoint string
|
||||
router http.Handler
|
||||
renderer *Renderer
|
||||
parser *form.Parser
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
staticStorage static.Storage
|
||||
//staticCache cache.Cache //TODO: enable when storage is implemented again
|
||||
endpoint string
|
||||
router http.Handler
|
||||
renderer *Renderer
|
||||
parser *form.Parser
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
staticStorage static.Storage
|
||||
authRepo auth_repository.Repository
|
||||
baseURL string
|
||||
consolePath string
|
||||
oidcAuthCallbackURL func(string) string
|
||||
oidcAuthCallbackURL func(context.Context, string) string
|
||||
idpConfigAlg crypto.EncryptionAlgorithm
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
iamDomain string
|
||||
@@ -46,7 +45,6 @@ type Config struct {
|
||||
LanguageCookieName string
|
||||
CSRFCookieName string
|
||||
Cache middleware.CacheConfig
|
||||
//StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -64,9 +62,10 @@ func CreateLogin(config Config,
|
||||
consolePath,
|
||||
domain,
|
||||
baseURL string,
|
||||
oidcAuthCallbackURL func(string) string,
|
||||
oidcAuthCallbackURL func(context.Context, string) string,
|
||||
externalSecure bool,
|
||||
userAgentCookie,
|
||||
issuerInterceptor,
|
||||
instanceHandler mux.MiddlewareFunc,
|
||||
userCodeAlg crypto.EncryptionAlgorithm,
|
||||
idpConfigAlg crypto.EncryptionAlgorithm,
|
||||
@@ -85,12 +84,6 @@ func CreateLogin(config Config,
|
||||
idpConfigAlg: idpConfigAlg,
|
||||
userCodeAlg: userCodeAlg,
|
||||
}
|
||||
//TODO: enable when storage is implemented again
|
||||
//login.staticCache, err = config.StaticCache.Config.NewCache()
|
||||
//if err != nil {
|
||||
// return nil, fmt.Errorf("unable to create storage cache: %w", err)
|
||||
//}
|
||||
|
||||
statikFS, err := fs.NewWithNamespace("login")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create filesystem: %w", err)
|
||||
@@ -105,7 +98,8 @@ func CreateLogin(config Config,
|
||||
return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err)
|
||||
}
|
||||
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
|
||||
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources))
|
||||
|
||||
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources), issuerInterceptor)
|
||||
login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage)
|
||||
login.parser = form.NewParser()
|
||||
return login, nil
|
||||
|
@@ -43,11 +43,11 @@ func (l *Login) renderSuccessAndCallback(w http.ResponseWriter, r *http.Request,
|
||||
userData: l.getUserData(r, authReq, "Login Successful", errID, errMessage),
|
||||
}
|
||||
if authReq != nil {
|
||||
data.RedirectURI = l.oidcAuthCallbackURL("") //the id will be set via the html (maybe change this with the login refactoring)
|
||||
data.RedirectURI = l.oidcAuthCallbackURL(r.Context(), "") //the id will be set via the html (maybe change this with the login refactoring)
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplLoginSuccess], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
|
||||
http.Redirect(w, r, l.oidcAuthCallbackURL(authReq.ID), http.StatusFound)
|
||||
http.Redirect(w, r, l.oidcAuthCallbackURL(r.Context(), authReq.ID), http.StatusFound)
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ type Commands struct {
|
||||
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
|
||||
|
||||
multifactors domain.MultifactorConfigs
|
||||
webauthn *webauthn_helper.WebAuthN
|
||||
webauthnConfig *webauthn_helper.Config
|
||||
keySize int
|
||||
keyAlgorithm crypto.EncryptionAlgorithm
|
||||
privateKeyLifetime time.Duration
|
||||
@@ -60,7 +60,7 @@ func StartCommands(es *eventstore.Eventstore,
|
||||
zitadelRoles []authz.RoleMapping,
|
||||
staticStore static.Storage,
|
||||
authZRepo authz_repo.Repository,
|
||||
webAuthN webauthn_helper.Config,
|
||||
webAuthN *webauthn_helper.Config,
|
||||
idpConfigEncryption,
|
||||
otpEncryption,
|
||||
smtpEncryption,
|
||||
@@ -84,6 +84,7 @@ func StartCommands(es *eventstore.Eventstore,
|
||||
userEncryption: userEncryption,
|
||||
domainVerificationAlg: domainVerificationEncryption,
|
||||
keyAlgorithm: oidcEncryption,
|
||||
webauthnConfig: webAuthN,
|
||||
}
|
||||
|
||||
instance_repo.RegisterEventMappers(repo.eventstore)
|
||||
@@ -107,11 +108,6 @@ func StartCommands(es *eventstore.Eventstore,
|
||||
|
||||
repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg)
|
||||
repo.domainVerificationValidator = http.ValidateDomain
|
||||
web, err := webauthn_helper.StartServer(webAuthN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo.webauthn = web
|
||||
|
||||
repo.tokenVerifier = authZRepo
|
||||
return repo, nil
|
||||
|
@@ -28,16 +28,6 @@ const (
|
||||
consolePostLogoutPath = console.HandlerPrefix + "/signedout"
|
||||
)
|
||||
|
||||
type AddInstance struct {
|
||||
InstanceName string
|
||||
CustomDomain string
|
||||
FirstOrgName string
|
||||
OwnerEmail string
|
||||
OwnerUsername string
|
||||
OwnerFirstName string
|
||||
OwnerLastName string
|
||||
}
|
||||
|
||||
type InstanceSetup struct {
|
||||
zitadel ZitadelConfig
|
||||
InstanceName string
|
||||
@@ -301,7 +291,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup, exte
|
||||
}
|
||||
|
||||
if setup.CustomDomain != "" {
|
||||
validations = append(validations, c.addInstanceDomain(instanceAgg, setup.CustomDomain, false))
|
||||
validations = append(validations, addInstanceDomain(instanceAgg, setup.CustomDomain, false))
|
||||
}
|
||||
|
||||
console := &addOIDCApp{
|
||||
|
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.addInstanceDomain(instanceAgg, instanceDomain, false)
|
||||
validation := addInstanceDomain(instanceAgg, instanceDomain, false)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -33,7 +33,7 @@ func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string)
|
||||
|
||||
func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.setPrimaryInstanceDomain(instanceAgg, instanceDomain)
|
||||
validation := setPrimaryInstanceDomain(instanceAgg, instanceDomain)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,7 +51,7 @@ func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain
|
||||
|
||||
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.removeInstanceDomain(instanceAgg, instanceDomain)
|
||||
validation := removeInstanceDomain(instanceAgg, instanceDomain)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -69,16 +69,16 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
|
||||
|
||||
func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation {
|
||||
domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain)
|
||||
return c.addInstanceDomain(a, domain, true)
|
||||
return addInstanceDomain(a, domain, true)
|
||||
}
|
||||
|
||||
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
|
||||
func addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
||||
events := []eventstore.Command{
|
||||
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
|
||||
}
|
||||
appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
|
||||
appWriteModel, err := getOIDCAppWriteModel(ctx, filter, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,13 +115,13 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||
func setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,13 +133,13 @@ func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomai
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||
func removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,11 +154,16 @@ func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain st
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) getInstanceDomainWriteModel(ctx context.Context, domain string) (*InstanceDomainWriteModel, error) {
|
||||
func getInstanceDomainWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, domain string) (*InstanceDomainWriteModel, error) {
|
||||
domainWriteModel := NewInstanceDomainWriteModel(ctx, domain)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, domainWriteModel)
|
||||
events, err := filter(ctx, domainWriteModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domainWriteModel, nil
|
||||
if len(events) == 0 {
|
||||
return domainWriteModel, nil
|
||||
}
|
||||
domainWriteModel.AppendEvents(events...)
|
||||
err = domainWriteModel.Reduce()
|
||||
return domainWriteModel, err
|
||||
}
|
||||
|
@@ -10,12 +10,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/keypair"
|
||||
)
|
||||
|
||||
const (
|
||||
oidcUser = "OIDC"
|
||||
)
|
||||
|
||||
func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||
ctx = setOIDCCtx(ctx)
|
||||
privateCrypto, publicCrypto, err := crypto.GenerateEncryptedKeyPair(c.keySize, c.keyAlgorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -28,8 +23,7 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
|
||||
privateKeyExp := time.Now().UTC().Add(c.privateKeyLifetime)
|
||||
publicKeyExp := time.Now().UTC().Add(c.publicKeyLifetime)
|
||||
|
||||
//TODO: InstanceID not available here?
|
||||
keyPairWriteModel := NewKeyPairWriteModel(keyID, "system") //TODO: change with multi issuer
|
||||
keyPairWriteModel := NewKeyPairWriteModel(keyID, authz.GetInstance(ctx).InstanceID())
|
||||
keyAgg := KeyPairAggregateFromWriteModel(&keyPairWriteModel.WriteModel)
|
||||
_, err = c.eventstore.Push(ctx, keypair.NewAddedEvent(
|
||||
ctx,
|
||||
@@ -40,8 +34,3 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
|
||||
privateKeyExp, publicKeyExp))
|
||||
return err
|
||||
}
|
||||
|
||||
func setOIDCCtx(ctx context.Context) context.Context {
|
||||
//TODO: InstanceID not available here?
|
||||
return authz.SetCtxData(ctx, authz.CtxData{UserID: oidcUser, OrgID: authz.GetInstance(ctx).InstanceID()})
|
||||
}
|
||||
|
@@ -3,8 +3,7 @@ package command
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
keypair "github.com/caos/zitadel/internal/repository/keypair"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
"github.com/caos/zitadel/internal/repository/keypair"
|
||||
)
|
||||
|
||||
type KeyPairWriteModel struct {
|
||||
@@ -52,7 +51,7 @@ func (wm *KeyPairWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(project.AggregateType).
|
||||
AggregateTypes(keypair.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(keypair.AddedEventType).
|
||||
Builder()
|
||||
|
@@ -321,3 +321,17 @@ func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, r
|
||||
}
|
||||
return appWriteModel, nil
|
||||
}
|
||||
|
||||
func getOIDCAppWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) {
|
||||
appWriteModel := NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
|
||||
events, err := filter(ctx, appWriteModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return appWriteModel, nil
|
||||
}
|
||||
appWriteModel.AppendEvents(events...)
|
||||
err = appWriteModel.Reduce()
|
||||
return appWriteModel, err
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/v2/pkg/oidc"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
@@ -157,7 +157,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner s
|
||||
if accountName == "" {
|
||||
accountName = user.EmailAddress
|
||||
}
|
||||
webAuthN, err := c.webauthn.BeginRegistration(user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||
webAuthN, err := c.webauthnConfig.BeginRegistration(ctx, user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -272,7 +272,7 @@ func (c *Commands) verifyHumanWebAuthN(ctx context.Context, userID, resourceowne
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
_, token := domain.GetTokenToVerify(tokens)
|
||||
webAuthN, err := c.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
||||
webAuthN, err := c.webauthnConfig.FinishRegistration(ctx, user, token, tokenName, credentialData, userAgentID != "")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -343,7 +343,7 @@ func (c *Commands) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
webAuthNLogin, err := c.webauthn.BeginLogin(human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||
webAuthNLogin, err := c.webauthnConfig.BeginLogin(ctx, human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -454,7 +454,7 @@ func (c *Commands) finishWebAuthNLogin(ctx context.Context, userID, resourceOwne
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
keyID, signCount, err := c.webauthn.FinishLogin(human, webAuthN, credentialData, isLoginUI, tokens...)
|
||||
keyID, signCount, err := c.webauthnConfig.FinishLogin(ctx, human, webAuthN, credentialData, isLoginUI, tokens...)
|
||||
if err != nil && keyID == nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
@@ -56,9 +56,7 @@ type Endpoints struct {
|
||||
}
|
||||
|
||||
type KeyConfig struct {
|
||||
Size int
|
||||
PrivateKeyLifetime time.Duration
|
||||
PublicKeyLifetime time.Duration
|
||||
SigningKeyRotationCheck time.Duration
|
||||
SigningKeyGracefulPeriod time.Duration
|
||||
Size int
|
||||
PrivateKeyLifetime time.Duration
|
||||
PublicKeyLifetime time.Duration
|
||||
}
|
||||
|
@@ -145,8 +145,8 @@ func (t *Translator) Lang(r *http.Request) language.Tag {
|
||||
return tag
|
||||
}
|
||||
|
||||
func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) {
|
||||
t.cookieHandler.SetCookie(w, t.cookieName, lang.String())
|
||||
func (t *Translator) SetLangCookie(w http.ResponseWriter, r *http.Request, lang language.Tag) {
|
||||
t.cookieHandler.SetCookie(w, t.cookieName, r.Host, lang.String())
|
||||
}
|
||||
|
||||
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
|
||||
@@ -177,7 +177,7 @@ func (t *Translator) langsFromCtx(ctx context.Context) []string {
|
||||
langs := t.preferredLanguages
|
||||
if ctx != nil {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
if ctxData.PreferredLanguage != "" {
|
||||
if ctxData.PreferredLanguage != language.Und.String() {
|
||||
langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage)
|
||||
}
|
||||
langs = append(langs, getAcceptLanguageHeader(ctx))
|
||||
@@ -190,6 +190,10 @@ func (t *Translator) SetPreferredLanguages(langs ...string) {
|
||||
}
|
||||
|
||||
func getAcceptLanguageHeader(ctx context.Context) string {
|
||||
acceptLanguage := metautils.ExtractIncoming(ctx).Get("accept-language")
|
||||
if acceptLanguage != "" {
|
||||
return acceptLanguage
|
||||
}
|
||||
return metautils.ExtractIncoming(ctx).Get("grpcgateway-accept-language")
|
||||
}
|
||||
|
||||
@@ -199,7 +203,7 @@ func localize(localizer *i18n.Localizer, id string, args map[string]interface{})
|
||||
TemplateData: args,
|
||||
})
|
||||
if err != nil {
|
||||
logging.LogWithFields("I18N-MsF5sx", "id", id, "args", args).WithError(err).Warnf("missing translation")
|
||||
logging.WithFields("id", id, "args", args).WithError(err).Warnf("missing translation")
|
||||
return id
|
||||
}
|
||||
return s
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -163,9 +164,9 @@ func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
|
||||
}
|
||||
|
||||
func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
|
||||
stmt, scan := prepareInstanceQuery(host)
|
||||
stmt, scan := prepareInstanceDomainQuery(host)
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
|
||||
InstanceDomainDomainCol.identifier(): strings.Split(host, ":")[0],
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement")
|
||||
@@ -279,3 +280,47 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
return sq.Select(
|
||||
InstanceColumnID.identifier(),
|
||||
InstanceColumnCreationDate.identifier(),
|
||||
InstanceColumnChangeDate.identifier(),
|
||||
InstanceColumnSequence.identifier(),
|
||||
InstanceColumnGlobalOrgID.identifier(),
|
||||
InstanceColumnProjectID.identifier(),
|
||||
InstanceColumnConsoleID.identifier(),
|
||||
InstanceColumnConsoleAppID.identifier(),
|
||||
InstanceColumnSetupStarted.identifier(),
|
||||
InstanceColumnSetupDone.identifier(),
|
||||
InstanceColumnDefaultLanguage.identifier(),
|
||||
).
|
||||
From(instanceTable.identifier()).
|
||||
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*Instance, error) {
|
||||
instance := &Instance{Host: host}
|
||||
lang := ""
|
||||
err := row.Scan(
|
||||
&instance.ID,
|
||||
&instance.CreationDate,
|
||||
&instance.ChangeDate,
|
||||
&instance.Sequence,
|
||||
&instance.GlobalOrgID,
|
||||
&instance.IAMProjectID,
|
||||
&instance.ConsoleID,
|
||||
&instance.ConsoleAppID,
|
||||
&instance.SetupStarted,
|
||||
&instance.SetupDone,
|
||||
&lang,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-n0wng", "Errors.IAM.NotFound")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
|
||||
}
|
||||
instance.DefaultLanguage = language.Make(lang)
|
||||
return instance, nil
|
||||
}
|
||||
}
|
||||
|
@@ -40,10 +40,9 @@ const (
|
||||
type KeyProjection struct {
|
||||
crdb.StatementHandler
|
||||
encryptionAlgorithm crypto.EncryptionAlgorithm
|
||||
keyChan chan<- interface{}
|
||||
}
|
||||
|
||||
func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) *KeyProjection {
|
||||
func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) *KeyProjection {
|
||||
p := new(KeyProjection)
|
||||
config.ProjectionName = KeyProjectionTable
|
||||
config.Reducers = p.reducers()
|
||||
@@ -56,7 +55,7 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
|
||||
crdb.NewColumn(KeyColumnInstanceID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(KeyColumnSequence, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(KeyColumnAlgorithm, crdb.ColumnTypeText, crdb.Default("")),
|
||||
crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeText, crdb.Default("")),
|
||||
crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeEnum, crdb.Default(0)),
|
||||
},
|
||||
crdb.NewPrimaryKey(KeyColumnInstanceID, KeyColumnID),
|
||||
crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{KeyColumnID})),
|
||||
@@ -79,7 +78,6 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
|
||||
),
|
||||
)
|
||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||
p.keyChan = keyChan
|
||||
p.encryptionAlgorithm = keyEncryptionAlgorithm
|
||||
|
||||
return p
|
||||
@@ -130,9 +128,6 @@ func (p *KeyProjection) reduceKeyPairAdded(event eventstore.Event) (*handler.Sta
|
||||
},
|
||||
crdb.WithTableSuffix(privateKeyTableSuffix),
|
||||
))
|
||||
if p.keyChan != nil {
|
||||
p.keyChan <- true
|
||||
}
|
||||
}
|
||||
if e.PublicKey.Expiry.After(time.Now()) {
|
||||
publicKey, err := crypto.Decrypt(e.PublicKey.Key, p.encryptionAlgorithm)
|
||||
|
@@ -17,7 +17,7 @@ const (
|
||||
FailedEventsTable = "projections.failed_events"
|
||||
)
|
||||
|
||||
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) error {
|
||||
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
|
||||
projectionConfig := crdb.StatementHandlerConfig{
|
||||
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
|
||||
HandlerConfig: handler.HandlerConfig{
|
||||
@@ -74,7 +74,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
|
||||
NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"]))
|
||||
NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"]))
|
||||
NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
|
||||
NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, keyChan)
|
||||
NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,7 @@ type Queries struct {
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
|
||||
func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
|
||||
func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
|
||||
statikLoginFS, err := fs.NewWithNamespace("login")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to start login statik dir")
|
||||
@@ -66,7 +66,7 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql
|
||||
keypair.RegisterEventMappers(repo.eventstore)
|
||||
usergrant.RegisterEventMappers(repo.eventstore)
|
||||
|
||||
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm, keyChan)
|
||||
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -2,38 +2,22 @@ package webauthn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/duo-labs/webauthn/protocol"
|
||||
"github.com/duo-labs/webauthn/webauthn"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type WebAuthN struct {
|
||||
webAuthN *webauthn.WebAuthn
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ID string
|
||||
Origin string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
func StartServer(config Config) (*WebAuthN, error) {
|
||||
webAuthN, err := webauthn.New(&webauthn.Config{
|
||||
RPDisplayName: config.DisplayName,
|
||||
RPID: config.ID,
|
||||
RPOrigin: config.Origin,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WebAuthN{
|
||||
webAuthN: webAuthN,
|
||||
}, err
|
||||
DisplayName string
|
||||
ExternalSecure bool
|
||||
}
|
||||
|
||||
type webUser struct {
|
||||
@@ -54,7 +38,10 @@ func (u *webUser) WebAuthnName() string {
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnDisplayName() string {
|
||||
return u.DisplayName
|
||||
if u.DisplayName != "" {
|
||||
return u.DisplayName
|
||||
}
|
||||
return u.GetUsername()
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnIcon() string {
|
||||
@@ -65,7 +52,11 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
|
||||
return u.credentials
|
||||
}
|
||||
|
||||
func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
|
||||
func (w *Config) BeginRegistration(ctx context.Context, user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
|
||||
webAuthNServer, err := w.serverFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds := WebAuthNsToCredentials(webAuthNs)
|
||||
existing := make([]protocol.CredentialDescriptor, len(creds))
|
||||
for i, cred := range creds {
|
||||
@@ -74,7 +65,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
|
||||
CredentialID: cred.ID,
|
||||
}
|
||||
}
|
||||
credentialOptions, sessionData, err := w.webAuthN.BeginRegistration(
|
||||
credentialOptions, sessionData, err := webAuthNServer.BeginRegistration(
|
||||
&webUser{
|
||||
Human: user,
|
||||
accountName: accountName,
|
||||
@@ -102,7 +93,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||
func (w *Config) FinishRegistration(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||
if webAuthN == nil {
|
||||
return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound")
|
||||
}
|
||||
@@ -113,7 +104,11 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
|
||||
return nil, caos_errs.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential")
|
||||
}
|
||||
sessionData := WebAuthNToSessionData(webAuthN)
|
||||
credential, err := w.webAuthN.CreateCredential(
|
||||
webAuthNServer, err := w.serverFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credential, err := webAuthNServer.CreateCredential(
|
||||
&webUser{
|
||||
Human: user,
|
||||
},
|
||||
@@ -132,8 +127,12 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
|
||||
return webAuthN, nil
|
||||
}
|
||||
|
||||
func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
|
||||
assertion, sessionData, err := w.webAuthN.BeginLogin(&webUser{
|
||||
func (w *Config) BeginLogin(ctx context.Context, user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
|
||||
webAuthNServer, err := w.serverFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assertion, sessionData, err := webAuthNServer.BeginLogin(&webUser{
|
||||
Human: user,
|
||||
credentials: WebAuthNsToCredentials(webAuthNs),
|
||||
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
|
||||
@@ -152,7 +151,7 @@ func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) {
|
||||
func (w *Config) FinishLogin(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) {
|
||||
assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData))
|
||||
if err != nil {
|
||||
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||
@@ -161,7 +160,11 @@ func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogi
|
||||
Human: user,
|
||||
credentials: WebAuthNsToCredentials(webAuthNs),
|
||||
}
|
||||
credential, err := w.webAuthN.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
|
||||
webAuthNServer, err := w.serverFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
credential, err := webAuthNServer.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
|
||||
if err != nil {
|
||||
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||
}
|
||||
@@ -171,3 +174,12 @@ func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogi
|
||||
}
|
||||
return credential.ID, credential.Authenticator.SignCount, nil
|
||||
}
|
||||
|
||||
func (w *Config) serverFromContext(ctx context.Context) (*webauthn.WebAuthn, error) {
|
||||
host := authz.GetInstance(ctx).RequestedDomain()
|
||||
return webauthn.New(&webauthn.Config{
|
||||
RPDisplayName: w.DisplayName,
|
||||
RPID: host,
|
||||
RPOrigin: http.BuildOrigin(host, w.ExternalSecure),
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user