mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
feat: improve instance not found error (#7413)
* feat: improve instance not found error * unit tests * check if is templatable * lint * assert * compile tests * remove error templates * link to instance not found page * fmt * cleanup * lint
This commit is contained in:
parent
062d153cfe
commit
f4c72cbe14
@ -360,7 +360,7 @@ func startAPIs(
|
||||
http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))),
|
||||
)
|
||||
limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, &config.Quotas.Access.AccessConfig)
|
||||
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor)
|
||||
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, config.ExternalDomain, limitingAccessInterceptor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating api %w", err)
|
||||
}
|
||||
@ -410,7 +410,7 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, config.ExternalDomain, login.IgnoreInstanceEndpoints...)
|
||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
|
||||
|
||||
|
@ -268,6 +268,8 @@ ZITADEL hosts everything under a single domain: `{instance}.zitadel.cloud` or yo
|
||||
|
||||
The domain is used as the OIDC issuer and as the base url for the gRPC and REST APIs, the Login and Console UI, which you'll find under `{your_domain}/ui/console/`.
|
||||
|
||||
Are you self-hosting and having troubles with *Instance not found* errors? [Check out this page](https://zitadel.com/docs/self-hosting/manage/custom-domain).
|
||||
|
||||
## API path prefixes
|
||||
|
||||
If you run ZITADEL on a custom domain, you may want to reuse that domain for other applications.
|
||||
|
@ -74,7 +74,8 @@ func New(
|
||||
queries *query.Queries,
|
||||
verifier internal_authz.APITokenVerifier,
|
||||
authZ internal_authz.Config,
|
||||
tlsConfig *tls.Config, http2HostName, http1HostName string,
|
||||
tlsConfig *tls.Config,
|
||||
http2HostName, http1HostName, externalDomain string,
|
||||
accessInterceptor *http_mw.AccessInterceptor,
|
||||
) (_ *API, err error) {
|
||||
api := &API{
|
||||
@ -87,7 +88,7 @@ func New(
|
||||
accessInterceptor: accessInterceptor,
|
||||
}
|
||||
|
||||
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService())
|
||||
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, externalDomain, tlsConfig, accessInterceptor.AccessService())
|
||||
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
zitadel_http "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/i18n"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@ -23,15 +24,15 @@ const (
|
||||
HTTP1Host = "x-zitadel-http1-host"
|
||||
)
|
||||
|
||||
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string, explicitInstanceIdServices ...string) grpc.UnaryServerInterceptor {
|
||||
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName, externalDomain string, explicitInstanceIdServices ...string) grpc.UnaryServerInterceptor {
|
||||
translator, err := i18n.NewZitadelTranslator(language.English)
|
||||
logging.OnError(err).Panic("unable to get translator")
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return setInstance(ctx, req, info, handler, verifier, headerName, translator, explicitInstanceIdServices...)
|
||||
return setInstance(ctx, req, info, handler, verifier, headerName, externalDomain, translator, explicitInstanceIdServices...)
|
||||
}
|
||||
}
|
||||
|
||||
func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string, translator *i18n.Translator, idFromRequestsServices ...string) (_ interface{}, err error) {
|
||||
func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName, externalDomain string, translator *i18n.Translator, idFromRequestsServices ...string) (_ interface{}, err error) {
|
||||
interceptorCtx, span := tracing.NewServerInterceptorSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
for _, service := range idFromRequestsServices {
|
||||
@ -55,16 +56,18 @@ func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInf
|
||||
return handler(authz.WithInstance(ctx, instance), req)
|
||||
}
|
||||
}
|
||||
|
||||
host, err := hostFromContext(interceptorCtx, headerName)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
instance, err := verifier.InstanceByHost(interceptorCtx, host)
|
||||
if err != nil {
|
||||
notFoundErr := new(zerrors.NotFoundError)
|
||||
if errors.As(err, ¬FoundErr) {
|
||||
notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil)
|
||||
err = fmt.Errorf("unable to set instance using origin %s (ExternalDomain is %s): %w", zitadel_http.ComposedOrigin(ctx), externalDomain, err)
|
||||
zErr := new(zerrors.ZitadelError)
|
||||
if errors.As(err, &zErr) {
|
||||
zErr.SetMessage(translator.LocalizeFromCtx(ctx, zErr.GetMessage(), nil))
|
||||
zErr.Parent = err
|
||||
err = zErr
|
||||
}
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ func Test_setInstance(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := setInstance(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier, tt.args.headerName, nil)
|
||||
got, err := setInstance(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier, tt.args.headerName, "", nil)
|
||||
if (err != nil) != tt.res.err {
|
||||
t.Errorf("setInstance() error = %v, wantErr %v", err, tt.res.err)
|
||||
return
|
||||
|
@ -39,6 +39,7 @@ func CreateServer(
|
||||
authConfig authz.Config,
|
||||
queries *query.Queries,
|
||||
hostHeaderName string,
|
||||
externalDomain string,
|
||||
tlsConfig *tls.Config,
|
||||
accessSvc *logstore.Service[*record.AccessLog],
|
||||
) *grpc.Server {
|
||||
@ -50,7 +51,7 @@ func CreateServer(
|
||||
middleware.DefaultTracingServer(),
|
||||
middleware.MetricsHandler(metricTypes, grpc_api.Probes...),
|
||||
middleware.NoCacheInterceptor(),
|
||||
middleware.InstanceInterceptor(queries, hostHeaderName, system_pb.SystemService_ServiceDesc.ServiceName, healthpb.Health_ServiceDesc.ServiceName),
|
||||
middleware.InstanceInterceptor(queries, hostHeaderName, externalDomain, system_pb.SystemService_ServiceDesc.ServiceName, healthpb.Health_ServiceDesc.ServiceName),
|
||||
middleware.AccessStorageInterceptor(accessSvc),
|
||||
middleware.ErrorHandler(),
|
||||
middleware.LimitsInterceptor(system_pb.SystemService_ServiceDesc.ServiceName),
|
||||
|
@ -19,16 +19,17 @@ import (
|
||||
)
|
||||
|
||||
type instanceInterceptor struct {
|
||||
verifier authz.InstanceVerifier
|
||||
headerName string
|
||||
ignoredPrefixes []string
|
||||
translator *i18n.Translator
|
||||
verifier authz.InstanceVerifier
|
||||
headerName, externalDomain string
|
||||
ignoredPrefixes []string
|
||||
translator *i18n.Translator
|
||||
}
|
||||
|
||||
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string, ignoredPrefixes ...string) *instanceInterceptor {
|
||||
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName, externalDomain string, ignoredPrefixes ...string) *instanceInterceptor {
|
||||
return &instanceInterceptor{
|
||||
verifier: verifier,
|
||||
headerName: headerName,
|
||||
externalDomain: externalDomain,
|
||||
ignoredPrefixes: ignoredPrefixes,
|
||||
translator: newZitadelTranslator(),
|
||||
}
|
||||
@ -55,9 +56,12 @@ func (a *instanceInterceptor) handleInstance(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
ctx, err := setInstance(r, a.verifier, a.headerName)
|
||||
if err != nil {
|
||||
caosErr := new(zerrors.NotFoundError)
|
||||
if errors.As(err, &caosErr) {
|
||||
caosErr.Message = a.translator.LocalizeFromRequest(r, caosErr.GetMessage(), nil)
|
||||
err = fmt.Errorf("unable to set instance using origin %s (ExternalDomain is %s): %w", zitadel_http.ComposedOrigin(r.Context()), a.externalDomain, err)
|
||||
zErr := new(zerrors.ZitadelError)
|
||||
if errors.As(err, &zErr) {
|
||||
zErr.SetMessage(a.translator.LocalizeFromRequest(r, zErr.GetMessage(), nil))
|
||||
zErr.Parent = err
|
||||
err = zErr
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
@ -68,13 +72,13 @@ func (a *instanceInterceptor) handleInstance(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
func setInstance(r *http.Request, verifier authz.InstanceVerifier, headerName string) (_ context.Context, err error) {
|
||||
ctx := r.Context()
|
||||
|
||||
authCtx, span := tracing.NewServerInterceptorSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
host, err := HostFromRequest(r, headerName)
|
||||
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-zWq7X", "Errors.Instance.NotFound")
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-zWq7X", "Errors.IAM.NotFound")
|
||||
}
|
||||
|
||||
instance, err := verifier.InstanceByHost(authCtx, host)
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -196,7 +197,12 @@ var (
|
||||
|
||||
func (q *Queries) InstanceByHost(ctx context.Context, host string) (_ authz.Instance, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to get instance by host %s: %w", host, err)
|
||||
}
|
||||
span.EndWithError(err)
|
||||
}()
|
||||
|
||||
domain := strings.Split(host, ":")[0] // remove possible port
|
||||
instance, scan := scanAuthzInstance(host, domain)
|
||||
|
@ -330,7 +330,7 @@ Errors:
|
||||
NotActive: Грантът по проекта не е активен
|
||||
NotInactive: Грантът по проекта не е неактивен
|
||||
IAM:
|
||||
NotFound: Екземплярът не е намерен. Вижте https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM не е намерен. Уверете се, че сте получили правилния домейн. Вижте https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Ролите не са сменени
|
||||
MemberInvalid: Членът е невалиден
|
||||
|
@ -327,7 +327,7 @@ Errors:
|
||||
NotActive: Grant projektu není aktivní
|
||||
NotInactive: Grant projektu není neaktivní
|
||||
IAM:
|
||||
NotFound: Instance nenalezena. Podívejte se na https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: Instance nebyla nalezena. Ujistěte se, že jste získali správnou doménu. Podívejte se na https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Role nebyly změněny
|
||||
MemberInvalid: Člen je neplatný
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: Projekt Grant ist nicht aktiv
|
||||
NotInactive: Projekt Grant ist nicht inaktiv
|
||||
IAM:
|
||||
NotFound: Instanz nicht gefunden. Schau dir https://zitadel.com/docs/self-hosting/manage/custom-domain an
|
||||
NotFound: Instanz nicht gefunden. Stelle sicher, dass Du die richtige Domain hast. Schau unter https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Rollen wurden nicht verändert
|
||||
MemberInvalid: Member ist ungültig
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: Project grant is not active
|
||||
NotInactive: Project grant is not inactive
|
||||
IAM:
|
||||
NotFound: Instance not found. Check out https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: Instance not found. Make sure you got the domain right. Check out https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Roles have not been changed
|
||||
MemberInvalid: Member is invalid
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: La concesión del proyecto no está activa
|
||||
NotInactive: La concesión del proyecto no está inactiva
|
||||
IAM:
|
||||
NotFound: Instancia no encontrada. Consulta https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: Instancia no encontrada. Asegúrate de que tienes el dominio correcto. Consulta https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Los roles no han cambiado
|
||||
MemberInvalid: El miembro no es válido
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: La subvention de projet n'est pas active
|
||||
NotInactive: La subvention du projet n'est pas inactive
|
||||
IAM:
|
||||
NotFound: Instance non trouvée. Consultez https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM non trouvé. Assurez-vous que vous avez la bonne organisation. Vérifiez https://zitadel.com/docs/apis/introduction#organizations
|
||||
Member:
|
||||
RolesNotChanged: Les rôles n'ont pas été modifiés
|
||||
MemberInvalid: Le membre n'est pas valide
|
||||
|
@ -329,7 +329,7 @@ Errors:
|
||||
NotActive: Grant del progetto non è attivo
|
||||
NotInactive: Grant del progetto non è inattivo
|
||||
IAM:
|
||||
NotFound: Istanza non trovata. Controlla https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM non trovato. Assicurati di avere il dominio corretto. Guarda su https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: I ruoli non sono stati cambiati
|
||||
MemberInvalid: Il membro non è valido
|
||||
|
@ -317,7 +317,7 @@ Errors:
|
||||
NotActive: プロジェクトグラントはアクティブではありません
|
||||
NotInactive: プロジェクトグラントは非アクティブではありません
|
||||
IAM:
|
||||
NotFound: インスタンスが見つかりません https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAMが見つかりません。正しいドメインを持っていることを確認してください。 https://zitadel.com/docs/apis/introduction#domains を参照してください
|
||||
Member:
|
||||
RolesNotChanged: ロールは変更されていません
|
||||
MemberInvalid: 無効なメンバーです
|
||||
|
@ -327,7 +327,7 @@ Errors:
|
||||
NotActive: Овластувањето за проектот не е активно
|
||||
NotInactive: Овластувањето за проектот не е неактивно
|
||||
IAM:
|
||||
NotFound: Инстанцата не е пронајдена. Проверете https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM не е пронајден. Проверете дали имате точен домен. Погледнете на https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Улогите не се променети
|
||||
MemberInvalid: Членот е невалиден
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: Projecttoekenning is niet actief
|
||||
NotInactive: Projecttoekenning is niet gedeactiveerd
|
||||
IAM:
|
||||
NotFound: Instantie niet gevonden. Bekijk https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM niet gevonden. Zorg ervoor dat u het juiste domein heeft. Kijk op https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Rollen zijn niet veranderd
|
||||
MemberInvalid: Lid is ongeldig
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: Grant projektu jest nieaktywny
|
||||
NotInactive: Grant projektu nie jest nieaktywny
|
||||
IAM:
|
||||
NotFound: Instancja nie znaleziona. Sprawdź https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM nie znaleziony. Upewnij się, że masz poprawną domenę. Sprawdź https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Role nie zmienione
|
||||
MemberInvalid: Członek jest nieprawidłowy
|
||||
|
@ -326,7 +326,7 @@ Errors:
|
||||
NotActive: A concessão do projeto não está ativa
|
||||
NotInactive: A concessão do projeto não está inativa
|
||||
IAM:
|
||||
NotFound: Instância não encontrada. Confira https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM não encontrado. Verifique se você tem o domínio correto. Consulte https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: As funções não foram alteradas
|
||||
MemberInvalid: O membro é inválido
|
||||
|
@ -321,7 +321,7 @@ Errors:
|
||||
NotActive: Грант проекта не активен
|
||||
NotInactive: Грант проекта не неактивен
|
||||
IAM:
|
||||
NotFound: Экземпляр не найден. Проверьте https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM не найден. Убедитесь, что у вас есть правильный домен. Смотрите на https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: Роли не изменились
|
||||
MemberInvalid: Участник недействителен
|
||||
|
@ -328,7 +328,7 @@ Errors:
|
||||
NotActive: 项目授权不是启用状态
|
||||
NotInactive: 项目授权不是停用状态
|
||||
IAM:
|
||||
NotFound: 实例未找到。查看 https://zitadel.com/docs/self-hosting/manage/custom-domain
|
||||
NotFound: IAM 未找到。确保您有正确的域。查看 https://zitadel.com/docs/apis/introduction#domains
|
||||
Member:
|
||||
RolesNotChanged: 角色没有改变
|
||||
MemberInvalid: 成员无效
|
||||
|
Loading…
Reference in New Issue
Block a user