Merge branch 'main' into grcp-server-reflect

This commit is contained in:
Livio Spring
2023-05-16 10:51:32 +02:00
committed by GitHub
68 changed files with 2333 additions and 296 deletions

View File

@@ -20,21 +20,22 @@ import (
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/logstore"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
type API struct {
port uint16
grpcServer *grpc.Server
verifier *internal_authz.TokenVerifier
health healthCheck
router *mux.Router
http1HostName string
grpcGateway *server.Gateway
healthServer *health.Server
port uint16
grpcServer *grpc.Server
verifier *internal_authz.TokenVerifier
health healthCheck
router *mux.Router
http1HostName string
grpcGateway *server.Gateway
healthServer *health.Server
accessInterceptor *http_mw.AccessInterceptor
queries *query.Queries
}
type healthCheck interface {
@@ -49,18 +50,20 @@ func New(
verifier *internal_authz.TokenVerifier,
authZ internal_authz.Config,
tlsConfig *tls.Config, http2HostName, http1HostName string,
accessSvc *logstore.Service,
accessInterceptor *http_mw.AccessInterceptor,
) (_ *API, err error) {
api := &API{
port: port,
verifier: verifier,
health: queries,
router: router,
http1HostName: http1HostName,
port: port,
verifier: verifier,
health: queries,
router: router,
http1HostName: http1HostName,
queries: queries,
accessInterceptor: accessInterceptor,
}
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessSvc)
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName)
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService())
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor)
if err != nil {
return nil, err
}
@@ -79,7 +82,14 @@ func New(
// used for v1 api (system, admin, mgmt, auth)
func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix) error {
grpcServer.RegisterServer(a.grpcServer)
handler, prefix, err := server.CreateGatewayWithPrefix(ctx, grpcServer, a.port, a.http1HostName)
handler, prefix, err := server.CreateGatewayWithPrefix(
ctx,
grpcServer,
a.port,
a.http1HostName,
a.accessInterceptor,
a.queries,
)
if err != nil {
return err
}

View File

@@ -1,8 +1,11 @@
package object
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
@@ -36,3 +39,13 @@ func ListQueryToQuery(query *object.ListQuery) (offset, limit uint64, asc bool)
}
return query.Offset, uint64(query.Limit), query.Asc
}
func ResourceOwnerFromReq(ctx context.Context, req *object.RequestContext) string {
if req.GetInstance() {
return authz.GetInstance(ctx).InstanceID()
}
if req.GetOrgId() != "" {
return req.GetOrgId()
}
return authz.GetCtxData(ctx).OrgID
}

View File

@@ -17,6 +17,7 @@ import (
client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware"
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/query"
)
const (
@@ -64,13 +65,15 @@ var (
)
type Gateway struct {
mux *runtime.ServeMux
http1HostName string
connection *grpc.ClientConn
mux *runtime.ServeMux
http1HostName string
connection *grpc.ClientConn
accessInterceptor *http_mw.AccessInterceptor
queries *query.Queries
}
func (g *Gateway) Handler() http.Handler {
return addInterceptors(g.mux, g.http1HostName)
return addInterceptors(g.mux, g.http1HostName, g.accessInterceptor, g.queries)
}
type CustomHTTPResponse interface {
@@ -79,7 +82,14 @@ type CustomHTTPResponse interface {
type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error
func CreateGatewayWithPrefix(ctx context.Context, g WithGatewayPrefix, port uint16, http1HostName string) (http.Handler, string, error) {
func CreateGatewayWithPrefix(
ctx context.Context,
g WithGatewayPrefix,
port uint16,
http1HostName string,
accessInterceptor *http_mw.AccessInterceptor,
queries *query.Queries,
) (http.Handler, string, error) {
runtimeMux := runtime.NewServeMux(serveMuxOptions...)
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
@@ -93,10 +103,10 @@ func CreateGatewayWithPrefix(ctx context.Context, g WithGatewayPrefix, port uint
if err != nil {
return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err)
}
return addInterceptors(runtimeMux, http1HostName), g.GatewayPathPrefix(), nil
return addInterceptors(runtimeMux, http1HostName, accessInterceptor, queries), g.GatewayPathPrefix(), nil
}
func CreateGateway(ctx context.Context, port uint16, http1HostName string) (*Gateway, error) {
func CreateGateway(ctx context.Context, port uint16, http1HostName string, accessInterceptor *http_mw.AccessInterceptor) (*Gateway, error) {
connection, err := dial(ctx,
port,
[]grpc.DialOption{
@@ -108,9 +118,10 @@ func CreateGateway(ctx context.Context, port uint16, http1HostName string) (*Gat
}
runtimeMux := runtime.NewServeMux(append(serveMuxOptions, runtime.WithHealthzEndpoint(healthpb.NewHealthClient(connection)))...)
return &Gateway{
mux: runtimeMux,
http1HostName: http1HostName,
connection: connection,
mux: runtimeMux,
http1HostName: http1HostName,
connection: connection,
accessInterceptor: accessInterceptor,
}, nil
}
@@ -145,13 +156,22 @@ func dial(ctx context.Context, port uint16, opts []grpc.DialOption) (*grpc.Clien
return conn, nil
}
func addInterceptors(handler http.Handler, http1HostName string) http.Handler {
func addInterceptors(
handler http.Handler,
http1HostName string,
accessInterceptor *http_mw.AccessInterceptor,
queries *query.Queries,
) http.Handler {
handler = http_mw.CallDurationHandler(handler)
handler = http1Host(handler, http1HostName)
handler = http_mw.CORSInterceptor(handler)
handler = http_mw.RobotsTagHandler(handler)
handler = http_mw.DefaultTelemetryHandler(handler)
return http_mw.DefaultMetricsHandler(handler)
// For some non-obvious reason, the exhaustedCookieInterceptor sends the SetCookie header
// only if it follows the http_mw.DefaultTelemetryHandler
handler = exhaustedCookieInterceptor(handler, accessInterceptor, queries)
handler = http_mw.DefaultMetricsHandler(handler)
return handler
}
func http1Host(next http.Handler, http1HostName string) http.Handler {
@@ -165,3 +185,35 @@ func http1Host(next http.Handler, http1HostName string) http.Handler {
next.ServeHTTP(w, r)
})
}
func exhaustedCookieInterceptor(
next http.Handler,
accessInterceptor *http_mw.AccessInterceptor,
queries *query.Queries,
) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
next.ServeHTTP(&cookieResponseWriter{
ResponseWriter: writer,
accessInterceptor: accessInterceptor,
request: request,
queries: queries,
}, request)
})
}
type cookieResponseWriter struct {
http.ResponseWriter
accessInterceptor *http_mw.AccessInterceptor
request *http.Request
queries *query.Queries
}
func (r *cookieResponseWriter) WriteHeader(status int) {
if status >= 200 && status < 300 {
r.accessInterceptor.DeleteExhaustedCookie(r.ResponseWriter, r.request)
}
if status == http.StatusTooManyRequests {
r.accessInterceptor.SetExhaustedCookie(r.ResponseWriter, r.request)
}
r.ResponseWriter.WriteHeader(status)
}

View File

@@ -0,0 +1,57 @@
package settings
import (
"context"
"google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/api/assets"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/query"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
)
var _ settings.SettingsServiceServer = (*Server)(nil)
type Server struct {
settings.UnimplementedSettingsServiceServer
command *command.Commands
query *query.Queries
assetsAPIDomain func(context.Context) string
}
type Config struct{}
func CreateServer(
command *command.Commands,
query *query.Queries,
externalSecure bool,
) *Server {
return &Server{
command: command,
query: query,
assetsAPIDomain: assets.AssetAPI(externalSecure),
}
}
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
settings.RegisterSettingsServiceServer(grpcServer, s)
}
func (s *Server) AppName() string {
return settings.SettingsService_ServiceDesc.ServiceName
}
func (s *Server) MethodPrefix() string {
return settings.SettingsService_ServiceDesc.ServiceName
}
func (s *Server) AuthMethods() authz.MethodMapping {
return settings.SettingsService_AuthMethods
}
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
return settings.RegisterSettingsServiceHandler
}

View File

@@ -0,0 +1,129 @@
package settings
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/api/grpc/text"
"github.com/zitadel/zitadel/internal/query"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
"github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
)
func (s *Server) GetLoginSettings(ctx context.Context, req *settings.GetLoginSettingsRequest) (*settings.GetLoginSettingsResponse, error) {
current, err := s.query.LoginPolicyByID(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
if err != nil {
return nil, err
}
return &settings.GetLoginSettingsResponse{
Settings: loginSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.OrgID,
},
}, nil
}
func (s *Server) GetPasswordComplexitySettings(ctx context.Context, req *settings.GetPasswordComplexitySettingsRequest) (*settings.GetPasswordComplexitySettingsResponse, error) {
current, err := s.query.PasswordComplexityPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
if err != nil {
return nil, err
}
return &settings.GetPasswordComplexitySettingsResponse{
Settings: passwordSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}, nil
}
func (s *Server) GetBrandingSettings(ctx context.Context, req *settings.GetBrandingSettingsRequest) (*settings.GetBrandingSettingsResponse, error) {
current, err := s.query.ActiveLabelPolicyByOrg(ctx, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
if err != nil {
return nil, err
}
return &settings.GetBrandingSettingsResponse{
Settings: brandingSettingsToPb(current, s.assetsAPIDomain(ctx)),
Details: &object_pb.Details{
Sequence: current.Sequence,
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}, nil
}
func (s *Server) GetDomainSettings(ctx context.Context, req *settings.GetDomainSettingsRequest) (*settings.GetDomainSettingsResponse, error) {
current, err := s.query.DomainPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
if err != nil {
return nil, err
}
return &settings.GetDomainSettingsResponse{
Settings: domainSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}, nil
}
func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *settings.GetLegalAndSupportSettingsRequest) (*settings.GetLegalAndSupportSettingsResponse, error) {
current, err := s.query.PrivacyPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
if err != nil {
return nil, err
}
return &settings.GetLegalAndSupportSettingsResponse{
Settings: legalAndSupportSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}, nil
}
func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockoutSettingsRequest) (*settings.GetLockoutSettingsResponse, error) {
current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
if err != nil {
return nil, err
}
return &settings.GetLockoutSettingsResponse{
Settings: lockoutSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}, nil
}
func (s *Server) GetActiveIdentityProviders(ctx context.Context, req *settings.GetActiveIdentityProvidersRequest) (*settings.GetActiveIdentityProvidersResponse, error) {
links, err := s.query.IDPLoginPolicyLinks(ctx, object.ResourceOwnerFromReq(ctx, req.GetCtx()), &query.IDPLoginPolicyLinksSearchQuery{}, false)
if err != nil {
return nil, err
}
return &settings.GetActiveIdentityProvidersResponse{
Details: object.ToListDetails(links.SearchResponse),
IdentityProviders: identityProvidersToPb(links.Links),
}, nil
}
func (s *Server) GetGeneralSettings(ctx context.Context, _ *settings.GetGeneralSettingsRequest) (*settings.GetGeneralSettingsResponse, error) {
langs, err := s.query.Languages(ctx)
if err != nil {
return nil, err
}
instance := authz.GetInstance(ctx)
return &settings.GetGeneralSettingsResponse{
SupportedLanguages: text.LanguageTagsToStrings(langs),
DefaultOrgId: instance.DefaultOrganisationID(),
DefaultLanguage: instance.DefaultLanguage().String(),
}, nil
}

View File

@@ -0,0 +1,189 @@
package settings
import (
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
)
// TODO: ?
func loginSettingsToPb(current *query.LoginPolicy) *settings.LoginSettings {
multi := make([]settings.MultiFactorType, len(current.MultiFactors))
for i, typ := range current.MultiFactors {
multi[i] = multiFactorTypeToPb(typ)
}
second := make([]settings.SecondFactorType, len(current.SecondFactors))
for i, typ := range current.SecondFactors {
second[i] = secondFactorTypeToPb(typ)
}
return &settings.LoginSettings{
AllowUsernamePassword: current.AllowUsernamePassword,
AllowRegister: current.AllowRegister,
AllowExternalIdp: current.AllowExternalIDPs,
ForceMfa: current.ForceMFA,
PasskeysType: passkeysTypeToPb(current.PasswordlessType),
HidePasswordReset: current.HidePasswordReset,
IgnoreUnknownUsernames: current.IgnoreUnknownUsernames,
AllowDomainDiscovery: current.AllowDomainDiscovery,
DisableLoginWithEmail: current.DisableLoginWithEmail,
DisableLoginWithPhone: current.DisableLoginWithPhone,
DefaultRedirectUri: current.DefaultRedirectURI,
PasswordCheckLifetime: durationpb.New(current.PasswordCheckLifetime),
ExternalLoginCheckLifetime: durationpb.New(current.ExternalLoginCheckLifetime),
MfaInitSkipLifetime: durationpb.New(current.MFAInitSkipLifetime),
SecondFactorCheckLifetime: durationpb.New(current.SecondFactorCheckLifetime),
MultiFactorCheckLifetime: durationpb.New(current.MultiFactorCheckLifetime),
SecondFactors: second,
MultiFactors: multi,
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
}
}
func isDefaultToResourceOwnerTypePb(isDefault bool) settings.ResourceOwnerType {
if isDefault {
return settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE
}
return settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_ORG
}
func passkeysTypeToPb(passwordlessType domain.PasswordlessType) settings.PasskeysType {
switch passwordlessType {
case domain.PasswordlessTypeAllowed:
return settings.PasskeysType_PASSKEYS_TYPE_ALLOWED
case domain.PasswordlessTypeNotAllowed:
return settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED
default:
return settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED
}
}
func secondFactorTypeToPb(secondFactorType domain.SecondFactorType) settings.SecondFactorType {
switch secondFactorType {
case domain.SecondFactorTypeOTP:
return settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP
case domain.SecondFactorTypeU2F:
return settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F
case domain.SecondFactorTypeUnspecified:
return settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED
default:
return settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED
}
}
func multiFactorTypeToPb(typ domain.MultiFactorType) settings.MultiFactorType {
switch typ {
case domain.MultiFactorTypeU2FWithPIN:
return settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION
case domain.MultiFactorTypeUnspecified:
return settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED
default:
return settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED
}
}
func passwordSettingsToPb(current *query.PasswordComplexityPolicy) *settings.PasswordComplexitySettings {
return &settings.PasswordComplexitySettings{
MinLength: current.MinLength,
RequiresUppercase: current.HasUppercase,
RequiresLowercase: current.HasLowercase,
RequiresNumber: current.HasNumber,
RequiresSymbol: current.HasSymbol,
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
}
}
func brandingSettingsToPb(current *query.LabelPolicy, assetPrefix string) *settings.BrandingSettings {
return &settings.BrandingSettings{
LightTheme: themeToPb(current.Light, assetPrefix, current.ResourceOwner),
DarkTheme: themeToPb(current.Dark, assetPrefix, current.ResourceOwner),
FontUrl: domain.AssetURL(assetPrefix, current.ResourceOwner, current.FontURL),
DisableWatermark: current.WatermarkDisabled,
HideLoginNameSuffix: current.HideLoginNameSuffix,
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
}
}
func themeToPb(theme query.Theme, assetPrefix, resourceOwner string) *settings.Theme {
return &settings.Theme{
PrimaryColor: theme.PrimaryColor,
BackgroundColor: theme.BackgroundColor,
FontColor: theme.FontColor,
WarnColor: theme.WarnColor,
LogoUrl: domain.AssetURL(assetPrefix, resourceOwner, theme.LogoURL),
IconUrl: domain.AssetURL(assetPrefix, resourceOwner, theme.IconURL),
}
}
func domainSettingsToPb(current *query.DomainPolicy) *settings.DomainSettings {
return &settings.DomainSettings{
LoginNameIncludesDomain: current.UserLoginMustBeDomain,
RequireOrgDomainVerification: current.ValidateOrgDomains,
SmtpSenderAddressMatchesInstanceDomain: current.SMTPSenderAddressMatchesInstanceDomain,
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
}
}
func legalAndSupportSettingsToPb(current *query.PrivacyPolicy) *settings.LegalAndSupportSettings {
return &settings.LegalAndSupportSettings{
TosLink: current.TOSLink,
PrivacyPolicyLink: current.PrivacyLink,
HelpLink: current.HelpLink,
SupportEmail: string(current.SupportEmail),
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
}
}
func lockoutSettingsToPb(current *query.LockoutPolicy) *settings.LockoutSettings {
return &settings.LockoutSettings{
MaxPasswordAttempts: current.MaxPasswordAttempts,
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
}
}
func identityProvidersToPb(idps []*query.IDPLoginPolicyLink) []*settings.IdentityProvider {
providers := make([]*settings.IdentityProvider, len(idps))
for i, idp := range idps {
providers[i] = identityProviderToPb(idp)
}
return providers
}
func identityProviderToPb(idp *query.IDPLoginPolicyLink) *settings.IdentityProvider {
return &settings.IdentityProvider{
Id: idp.IDPID,
Name: domain.IDPName(idp.IDPName, idp.IDPType),
Type: idpTypeToPb(idp.IDPType),
}
}
func idpTypeToPb(idpType domain.IDPType) settings.IdentityProviderType {
switch idpType {
case domain.IDPTypeUnspecified:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED
case domain.IDPTypeOIDC:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC
case domain.IDPTypeJWT:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_JWT
case domain.IDPTypeOAuth:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OAUTH
case domain.IDPTypeLDAP:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_LDAP
case domain.IDPTypeAzureAD:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_AZURE_AD
case domain.IDPTypeGitHub:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB
case domain.IDPTypeGitHubEnterprise:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB_ES
case domain.IDPTypeGitLab:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB
case domain.IDPTypeGitLabSelfHosted:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB_SELF_HOSTED
case domain.IDPTypeGoogle:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GOOGLE
default:
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED
}
}

View File

@@ -0,0 +1,461 @@
package settings
import (
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
)
var ignoreMessageTypes = map[protoreflect.FullName]bool{
"google.protobuf.Duration": true,
}
// allFieldsSet recusively checks if all values in a message
// have a non-zero value.
func allFieldsSet(t testing.TB, msg protoreflect.Message) {
md := msg.Descriptor()
name := md.FullName()
if ignoreMessageTypes[name] {
return
}
fields := md.Fields()
for i := 0; i < fields.Len(); i++ {
fd := fields.Get(i)
if !msg.Has(fd) {
t.Errorf("not all fields set in %q, missing %q", name, fd.Name())
continue
}
if fd.Kind() == protoreflect.MessageKind {
allFieldsSet(t, msg.Get(fd).Message())
}
}
}
func Test_loginSettingsToPb(t *testing.T) {
arg := &query.LoginPolicy{
AllowUsernamePassword: true,
AllowRegister: true,
AllowExternalIDPs: true,
ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
DefaultRedirectURI: "example.com",
PasswordCheckLifetime: time.Hour,
ExternalLoginCheckLifetime: time.Minute,
MFAInitSkipLifetime: time.Millisecond,
SecondFactorCheckLifetime: time.Microsecond,
MultiFactorCheckLifetime: time.Nanosecond,
SecondFactors: []domain.SecondFactorType{
domain.SecondFactorTypeOTP,
domain.SecondFactorTypeU2F,
},
MultiFactors: []domain.MultiFactorType{
domain.MultiFactorTypeU2FWithPIN,
},
IsDefault: true,
}
want := &settings.LoginSettings{
AllowUsernamePassword: true,
AllowRegister: true,
AllowExternalIdp: true,
ForceMfa: true,
PasskeysType: settings.PasskeysType_PASSKEYS_TYPE_ALLOWED,
HidePasswordReset: true,
IgnoreUnknownUsernames: true,
AllowDomainDiscovery: true,
DisableLoginWithEmail: true,
DisableLoginWithPhone: true,
DefaultRedirectUri: "example.com",
PasswordCheckLifetime: durationpb.New(time.Hour),
ExternalLoginCheckLifetime: durationpb.New(time.Minute),
MfaInitSkipLifetime: durationpb.New(time.Millisecond),
SecondFactorCheckLifetime: durationpb.New(time.Microsecond),
MultiFactorCheckLifetime: durationpb.New(time.Nanosecond),
SecondFactors: []settings.SecondFactorType{
settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP,
settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F,
},
MultiFactors: []settings.MultiFactorType{
settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION,
},
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
}
got := loginSettingsToPb(arg)
allFieldsSet(t, got.ProtoReflect())
if !proto.Equal(got, want) {
t.Errorf("loginSettingsToPb() =\n%v\nwant\n%v", got, want)
}
}
func Test_isDefaultToResourceOwnerTypePb(t *testing.T) {
type args struct {
isDefault bool
}
tests := []struct {
args args
want settings.ResourceOwnerType
}{
{
args: args{false},
want: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_ORG,
},
{
args: args{true},
want: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
},
}
for _, tt := range tests {
t.Run(tt.want.String(), func(t *testing.T) {
got := isDefaultToResourceOwnerTypePb(tt.args.isDefault)
assert.Equal(t, tt.want, got)
})
}
}
func Test_passkeysTypeToPb(t *testing.T) {
type args struct {
passwordlessType domain.PasswordlessType
}
tests := []struct {
args args
want settings.PasskeysType
}{
{
args: args{domain.PasswordlessTypeNotAllowed},
want: settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED,
},
{
args: args{domain.PasswordlessTypeAllowed},
want: settings.PasskeysType_PASSKEYS_TYPE_ALLOWED,
},
{
args: args{99},
want: settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED,
},
}
for _, tt := range tests {
t.Run(tt.want.String(), func(t *testing.T) {
got := passkeysTypeToPb(tt.args.passwordlessType)
assert.Equal(t, tt.want, got)
})
}
}
func Test_secondFactorTypeToPb(t *testing.T) {
type args struct {
secondFactorType domain.SecondFactorType
}
tests := []struct {
args args
want settings.SecondFactorType
}{
{
args: args{domain.SecondFactorTypeOTP},
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP,
},
{
args: args{domain.SecondFactorTypeU2F},
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F,
},
{
args: args{domain.SecondFactorTypeUnspecified},
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED,
},
{
args: args{99},
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED,
},
}
for _, tt := range tests {
t.Run(tt.want.String(), func(t *testing.T) {
got := secondFactorTypeToPb(tt.args.secondFactorType)
assert.Equal(t, tt.want, got)
})
}
}
func Test_multiFactorTypeToPb(t *testing.T) {
type args struct {
typ domain.MultiFactorType
}
tests := []struct {
args args
want settings.MultiFactorType
}{
{
args: args{domain.MultiFactorTypeU2FWithPIN},
want: settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION,
},
{
args: args{domain.MultiFactorTypeUnspecified},
want: settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED,
},
{
args: args{99},
want: settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED,
},
}
for _, tt := range tests {
t.Run(tt.want.String(), func(t *testing.T) {
got := multiFactorTypeToPb(tt.args.typ)
assert.Equal(t, tt.want, got)
})
}
}
func Test_passwordSettingsToPb(t *testing.T) {
arg := &query.PasswordComplexityPolicy{
MinLength: 12,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
IsDefault: true,
}
want := &settings.PasswordComplexitySettings{
MinLength: 12,
RequiresUppercase: true,
RequiresLowercase: true,
RequiresNumber: true,
RequiresSymbol: true,
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
}
got := passwordSettingsToPb(arg)
allFieldsSet(t, got.ProtoReflect())
if !proto.Equal(got, want) {
t.Errorf("passwordSettingsToPb() =\n%v\nwant\n%v", got, want)
}
}
func Test_brandingSettingsToPb(t *testing.T) {
arg := &query.LabelPolicy{
Light: query.Theme{
PrimaryColor: "red",
WarnColor: "white",
BackgroundColor: "blue",
FontColor: "orange",
LogoURL: "light-logo",
IconURL: "light-icon",
},
Dark: query.Theme{
PrimaryColor: "magenta",
WarnColor: "pink",
BackgroundColor: "black",
FontColor: "white",
LogoURL: "dark-logo",
IconURL: "dark-icon",
},
ResourceOwner: "me",
FontURL: "fonts",
WatermarkDisabled: true,
HideLoginNameSuffix: true,
IsDefault: true,
}
want := &settings.BrandingSettings{
LightTheme: &settings.Theme{
PrimaryColor: "red",
WarnColor: "white",
BackgroundColor: "blue",
FontColor: "orange",
LogoUrl: "http://example.com/me/light-logo",
IconUrl: "http://example.com/me/light-icon",
},
DarkTheme: &settings.Theme{
PrimaryColor: "magenta",
WarnColor: "pink",
BackgroundColor: "black",
FontColor: "white",
LogoUrl: "http://example.com/me/dark-logo",
IconUrl: "http://example.com/me/dark-icon",
},
FontUrl: "http://example.com/me/fonts",
DisableWatermark: true,
HideLoginNameSuffix: true,
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
}
got := brandingSettingsToPb(arg, "http://example.com")
allFieldsSet(t, got.ProtoReflect())
if !proto.Equal(got, want) {
t.Errorf("brandingSettingsToPb() =\n%v\nwant\n%v", got, want)
}
}
func Test_domainSettingsToPb(t *testing.T) {
arg := &query.DomainPolicy{
UserLoginMustBeDomain: true,
ValidateOrgDomains: true,
SMTPSenderAddressMatchesInstanceDomain: true,
IsDefault: true,
}
want := &settings.DomainSettings{
LoginNameIncludesDomain: true,
RequireOrgDomainVerification: true,
SmtpSenderAddressMatchesInstanceDomain: true,
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
}
got := domainSettingsToPb(arg)
allFieldsSet(t, got.ProtoReflect())
if !proto.Equal(got, want) {
t.Errorf("domainSettingsToPb() =\n%v\nwant\n%v", got, want)
}
}
func Test_legalSettingsToPb(t *testing.T) {
arg := &query.PrivacyPolicy{
TOSLink: "http://example.com/tos",
PrivacyLink: "http://example.com/pricacy",
HelpLink: "http://example.com/help",
SupportEmail: "support@zitadel.com",
IsDefault: true,
}
want := &settings.LegalAndSupportSettings{
TosLink: "http://example.com/tos",
PrivacyPolicyLink: "http://example.com/pricacy",
HelpLink: "http://example.com/help",
SupportEmail: "support@zitadel.com",
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
}
got := legalAndSupportSettingsToPb(arg)
allFieldsSet(t, got.ProtoReflect())
if !proto.Equal(got, want) {
t.Errorf("legalSettingsToPb() =\n%v\nwant\n%v", got, want)
}
}
func Test_lockoutSettingsToPb(t *testing.T) {
arg := &query.LockoutPolicy{
MaxPasswordAttempts: 22,
IsDefault: true,
}
want := &settings.LockoutSettings{
MaxPasswordAttempts: 22,
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
}
got := lockoutSettingsToPb(arg)
allFieldsSet(t, got.ProtoReflect())
if !proto.Equal(got, want) {
t.Errorf("lockoutSettingsToPb() =\n%v\nwant\n%v", got, want)
}
}
func Test_identityProvidersToPb(t *testing.T) {
arg := []*query.IDPLoginPolicyLink{
{
IDPID: "1",
IDPName: "foo",
IDPType: domain.IDPTypeOIDC,
},
{
IDPID: "2",
IDPName: "bar",
IDPType: domain.IDPTypeGitHub,
},
}
want := []*settings.IdentityProvider{
{
Id: "1",
Name: "foo",
Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC,
},
{
Id: "2",
Name: "bar",
Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB,
},
}
got := identityProvidersToPb(arg)
require.Len(t, got, len(got))
for i, v := range got {
allFieldsSet(t, v.ProtoReflect())
if !proto.Equal(v, want[i]) {
t.Errorf("identityProvidersToPb() =\n%v\nwant\n%v", got, want)
}
}
}
func Test_idpTypeToPb(t *testing.T) {
type args struct {
idpType domain.IDPType
}
tests := []struct {
args args
want settings.IdentityProviderType
}{
{
args: args{domain.IDPTypeUnspecified},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED,
},
{
args: args{domain.IDPTypeOIDC},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC,
},
{
args: args{domain.IDPTypeJWT},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_JWT,
},
{
args: args{domain.IDPTypeOAuth},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OAUTH,
},
{
args: args{domain.IDPTypeLDAP},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_LDAP,
},
{
args: args{domain.IDPTypeAzureAD},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_AZURE_AD,
},
{
args: args{domain.IDPTypeGitHub},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB,
},
{
args: args{domain.IDPTypeGitHubEnterprise},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB_ES,
},
{
args: args{domain.IDPTypeGitLab},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB,
},
{
args: args{domain.IDPTypeGitLabSelfHosted},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB_SELF_HOSTED,
},
{
args: args{domain.IDPTypeGoogle},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GOOGLE,
},
{
args: args{99},
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED,
},
}
for _, tt := range tests {
t.Run(tt.want.String(), func(t *testing.T) {
if got := idpTypeToPb(tt.args.idpType); !reflect.DeepEqual(got, tt.want) {
t.Errorf("idpTypeToPb() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,15 +1,17 @@
package middleware
import (
"math"
"context"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/logstore"
"github.com/zitadel/zitadel/internal/logstore/emitters/access"
@@ -20,6 +22,7 @@ type AccessInterceptor struct {
svc *logstore.Service
cookieHandler *http_utils.CookieHandler
limitConfig *AccessConfig
storeOnly bool
}
type AccessConfig struct {
@@ -27,54 +30,85 @@ type AccessConfig struct {
ExhaustedCookieMaxAge time.Duration
}
func NewAccessInterceptor(svc *logstore.Service, cookieConfig *AccessConfig) *AccessInterceptor {
// NewAccessInterceptor intercepts all requests and stores them to the logstore.
// If storeOnly is false, it also checks if requests are exhausted.
// If requests are exhausted, it also returns http.StatusTooManyRequests and sets a cookie
func NewAccessInterceptor(svc *logstore.Service, cookieHandler *http_utils.CookieHandler, cookieConfig *AccessConfig) *AccessInterceptor {
return &AccessInterceptor{
svc: svc,
cookieHandler: http_utils.NewCookieHandler(
http_utils.WithUnsecure(),
http_utils.WithMaxAge(int(math.Floor(cookieConfig.ExhaustedCookieMaxAge.Seconds()))),
),
limitConfig: cookieConfig,
svc: svc,
cookieHandler: cookieHandler,
limitConfig: cookieConfig,
}
}
func (a *AccessInterceptor) WithoutLimiting() *AccessInterceptor {
return &AccessInterceptor{
svc: a.svc,
cookieHandler: a.cookieHandler,
limitConfig: a.limitConfig,
storeOnly: true,
}
}
func (a *AccessInterceptor) AccessService() *logstore.Service {
return a.svc
}
func (a *AccessInterceptor) Limit(ctx context.Context) bool {
if !a.svc.Enabled() || a.storeOnly {
return false
}
instance := authz.GetInstance(ctx)
remaining := a.svc.Limit(ctx, instance.InstanceID())
return remaining != nil && *remaining <= 0
}
func (a *AccessInterceptor) SetExhaustedCookie(writer http.ResponseWriter, request *http.Request) {
cookieValue := "true"
host := request.Header.Get(middleware.HTTP1Host)
domain := host
if strings.ContainsAny(host, ":") {
var err error
domain, _, err = net.SplitHostPort(host)
if err != nil {
logging.WithError(err).WithField("host", host).Warning("failed to extract cookie domain from request host")
}
}
a.cookieHandler.SetCookie(writer, a.limitConfig.ExhaustedCookieKey, domain, cookieValue)
}
func (a *AccessInterceptor) DeleteExhaustedCookie(writer http.ResponseWriter, request *http.Request) {
a.cookieHandler.DeleteCookie(writer, request, a.limitConfig.ExhaustedCookieKey)
}
func (a *AccessInterceptor) Handle(next http.Handler) http.Handler {
if !a.svc.Enabled() {
return next
}
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
var err error
tracingCtx, checkSpan := tracing.NewNamedSpan(ctx, "checkAccess")
wrappedWriter := &statusRecorder{ResponseWriter: writer, status: 0}
instance := authz.GetInstance(ctx)
remaining := a.svc.Limit(tracingCtx, instance.InstanceID())
limit := remaining != nil && *remaining == 0
a.cookieHandler.SetCookie(wrappedWriter, a.limitConfig.ExhaustedCookieKey, request.Host, strconv.FormatBool(limit))
if limit {
wrappedWriter.WriteHeader(http.StatusTooManyRequests)
wrappedWriter.ignoreWrites = true
}
limited := a.Limit(tracingCtx)
checkSpan.End()
next.ServeHTTP(wrappedWriter, request)
if limited {
a.SetExhaustedCookie(wrappedWriter, request)
http.Error(wrappedWriter, "quota for authenticated requests is exhausted", http.StatusTooManyRequests)
}
if !limited && !a.storeOnly {
a.DeleteExhaustedCookie(wrappedWriter, request)
}
if !limited {
next.ServeHTTP(wrappedWriter, request)
}
tracingCtx, writeSpan := tracing.NewNamedSpan(tracingCtx, "writeAccess")
defer writeSpan.End()
requestURL := request.RequestURI
unescapedURL, err := url.QueryUnescape(requestURL)
if err != nil {
logging.WithError(err).WithField("url", requestURL).Warning("failed to unescape request url")
// err = nil is effective because of deferred tracing span end
err = nil
}
instance := authz.GetInstance(tracingCtx)
a.svc.Handle(tracingCtx, &access.Record{
LogDate: time.Now(),
Protocol: access.HTTP,

View File

@@ -1,9 +1,11 @@
package console
import (
"bytes"
"embed"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
@@ -22,8 +24,9 @@ import (
)
type Config struct {
ShortCache middleware.CacheConfig
LongCache middleware.CacheConfig
ShortCache middleware.CacheConfig
LongCache middleware.CacheConfig
InstanceManagementURL string
}
type spaHandler struct {
@@ -88,7 +91,7 @@ func (f *file) Stat() (_ fs.FileInfo, err error) {
return f, nil
}
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler, accessInterceptor func(http.Handler) http.Handler, customerPortal string) (http.Handler, error) {
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler func(http.Handler) http.Handler, limitingAccessInterceptor *middleware.AccessInterceptor, customerPortal string) (http.Handler, error) {
fSys, err := fs.Sub(static, "static")
if err != nil {
return nil, err
@@ -103,14 +106,26 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
handler := mux.NewRouter()
handler.Use(callDurationInterceptor, instanceHandler, security, accessInterceptor)
handler.Use(callDurationInterceptor, instanceHandler, security, limitingAccessInterceptor.WithoutLimiting().Handle)
handler.Handle(envRequestPath, middleware.TelemetryHandler()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
url := http_util.BuildOrigin(r.Host, externalSecure)
environmentJSON, err := createEnvironmentJSON(url, issuer(r), authz.GetInstance(r.Context()).ConsoleClientID(), customerPortal)
ctx := r.Context()
instance := authz.GetInstance(ctx)
instanceMgmtURL, err := templateInstanceManagementURL(config.InstanceManagementURL, instance)
if err != nil {
http.Error(w, fmt.Sprintf("unable to template instance management url for console: %v", err), http.StatusInternalServerError)
return
}
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID(), customerPortal, instanceMgmtURL)
if err != nil {
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
return
}
if limitingAccessInterceptor.Limit(ctx) {
limitingAccessInterceptor.SetExhaustedCookie(w, r)
} else {
limitingAccessInterceptor.DeleteExhaustedCookie(w, r)
}
_, err = w.Write(environmentJSON)
logging.OnError(err).Error("error serving environment.json")
})))
@@ -118,6 +133,18 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
return handler, nil
}
func templateInstanceManagementURL(templateableCookieValue string, instance authz.Instance) (string, error) {
cookieValueTemplate, err := template.New("cookievalue").Parse(templateableCookieValue)
if err != nil {
return templateableCookieValue, err
}
cookieValue := new(bytes.Buffer)
if err = cookieValueTemplate.Execute(cookieValue, instance); err != nil {
return templateableCookieValue, err
}
return cookieValue.String(), nil
}
func csp() *middleware.CSP {
csp := middleware.DefaultSCP
csp.StyleSrc = csp.StyleSrc.AddInline()
@@ -127,17 +154,19 @@ func csp() *middleware.CSP {
return &csp
}
func createEnvironmentJSON(api, issuer, clientID, customerPortal string) ([]byte, error) {
func createEnvironmentJSON(api, issuer, clientID, customerPortal, instanceMgmtUrl string) ([]byte, error) {
environment := struct {
API string `json:"api,omitempty"`
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"clientid,omitempty"`
CustomerPortal string `json:"customer_portal,omitempty"`
API string `json:"api,omitempty"`
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"clientid,omitempty"`
CustomerPortal string `json:"customer_portal,omitempty"`
InstanceManagementURL string `json:"instance_management_url,omitempty"`
}{
API: api,
Issuer: issuer,
ClientID: clientID,
CustomerPortal: customerPortal,
API: api,
Issuer: issuer,
ClientID: clientID,
CustomerPortal: customerPortal,
InstanceManagementURL: instanceMgmtUrl,
}
return json.Marshal(environment)
}

View File

@@ -327,7 +327,12 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
var msg string
if err != nil {
logging.WithError(err).WithField("auth_req_id", authReq.ID).Error()
log := logging.WithError(err)
if authReq != nil {
log = log.WithField("auth_req_id", authReq.ID)
}
log.Error()
_, msg = l.getErrorMessage(r, err)
}
data := l.getBaseData(r, authReq, "Errors.Internal", "", "Internal", msg)