Merge branch 'main' into perf-introspecion

This commit is contained in:
Tim Möhlmann
2023-11-13 18:16:32 +02:00
186 changed files with 11778 additions and 1466 deletions

View File

@@ -63,7 +63,7 @@ func New(
}
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService())
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor)
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor, tlsConfig)
if err != nil {
return nil, err
}
@@ -80,7 +80,7 @@ func New(
// creates a new grpc gateway and registers it as a separate http handler
//
// used for v1 api (system, admin, mgmt, auth)
func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix) error {
func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix, tlsConfig *tls.Config) error {
grpcServer.RegisterServer(a.grpcServer)
handler, prefix, err := server.CreateGatewayWithPrefix(
ctx,
@@ -89,6 +89,7 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayP
a.http1HostName,
a.accessInterceptor,
a.queries,
tlsConfig,
)
if err != nil {
return err

View File

@@ -60,7 +60,6 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest)
AwaitOpenTransactions().
ResourceOwner(req.ResourceOwner).
EditorUser(req.EditorUserId).
CreationDateAfter(req.CreationDate.AsTime()).
SequenceGreater(req.Sequence)
if len(aggregateIDs) > 0 || len(aggregateTypes) > 0 || len(eventTypes) > 0 {
@@ -71,8 +70,11 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest)
Builder()
}
if req.Asc {
if req.GetAsc() {
builder.OrderAsc()
builder.CreationDateAfter(req.CreationDate.AsTime())
} else {
builder.CreationDateBefore(req.CreationDate.AsTime())
}
return builder, nil

View File

@@ -1,7 +1,7 @@
package event
import (
structpb "github.com/golang/protobuf/ptypes/struct"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/errors"

View File

@@ -24,7 +24,7 @@ import (
func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := user_grpc.UserQueriesToQuery(req.Queries)
queries, err := user_grpc.UserQueriesToQuery(req.Queries, 0 /*start from level 0*/)
if err != nil {
return nil, err
}

View File

@@ -2,6 +2,7 @@ package server
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"strings"
@@ -9,6 +10,7 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/zitadel/logging"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/protobuf/encoding/protojson"
@@ -89,10 +91,11 @@ func CreateGatewayWithPrefix(
http1HostName string,
accessInterceptor *http_mw.AccessInterceptor,
queries *query.Queries,
tlsConfig *tls.Config,
) (http.Handler, string, error) {
runtimeMux := runtime.NewServeMux(serveMuxOptions...)
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithTransportCredentials(grpcCredentials(tlsConfig)),
grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()),
}
connection, err := dial(ctx, port, opts)
@@ -106,11 +109,17 @@ func CreateGatewayWithPrefix(
return addInterceptors(runtimeMux, http1HostName, accessInterceptor, queries), g.GatewayPathPrefix(), nil
}
func CreateGateway(ctx context.Context, port uint16, http1HostName string, accessInterceptor *http_mw.AccessInterceptor) (*Gateway, error) {
func CreateGateway(
ctx context.Context,
port uint16,
http1HostName string,
accessInterceptor *http_mw.AccessInterceptor,
tlsConfig *tls.Config,
) (*Gateway, error) {
connection, err := dial(ctx,
port,
[]grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithTransportCredentials(grpcCredentials(tlsConfig)),
grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()),
})
if err != nil {
@@ -217,3 +226,15 @@ func (r *cookieResponseWriter) WriteHeader(status int) {
}
r.ResponseWriter.WriteHeader(status)
}
func grpcCredentials(tlsConfig *tls.Config) credentials.TransportCredentials {
creds := insecure.NewCredentials()
if tlsConfig != nil {
tlsConfigClone := tlsConfig.Clone()
// We don't want to verify the certificate of the internal grpc server
// That's up to the client who called the gRPC gateway
tlsConfigClone.InsecureSkipVerify = true
creds = credentials.NewTLS(tlsConfigClone)
}
return creds
}

View File

@@ -3,12 +3,11 @@ package server
import (
"context"
"github.com/golang/protobuf/ptypes/empty"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/zitadel/logging"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/structpb"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/proto"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
@@ -22,24 +21,23 @@ func NewValidator(validations map[string]ValidationFunction) *Validator {
return &Validator{validations: validations}
}
func (v *Validator) Healthz(_ context.Context, e *empty.Empty) (*empty.Empty, error) {
func (v *Validator) Healthz(_ context.Context, e *emptypb.Empty) (*emptypb.Empty, error) {
return e, nil
}
func (v *Validator) Ready(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {
func (v *Validator) Ready(ctx context.Context, e *emptypb.Empty) (*emptypb.Empty, error) {
if len(validate(ctx, v.validations)) == 0 {
return e, nil
}
return nil, errors.ThrowInternal(nil, "API-2jD9a", "not ready")
}
func (v *Validator) Validate(ctx context.Context, _ *empty.Empty) (*structpb.Struct, error) {
validations := validate(ctx, v.validations)
return proto.ToPBStruct(validations)
func (v *Validator) Validate(ctx context.Context, _ *emptypb.Empty) (*structpb.Struct, error) {
return structpb.NewStruct(validate(ctx, v.validations))
}
func validate(ctx context.Context, validations map[string]ValidationFunction) map[string]error {
errors := make(map[string]error)
func validate(ctx context.Context, validations map[string]ValidationFunction) map[string]any {
errors := make(map[string]any)
for id, validation := range validations {
if err := validation(ctx); err != nil {
logging.Log("API-vf823").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Error("validation failed")

View File

@@ -5,7 +5,7 @@ import (
"reflect"
"testing"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/protobuf/types/known/emptypb"
"github.com/zitadel/zitadel/internal/errors"
)
@@ -15,7 +15,7 @@ func TestValidator_Healthz(t *testing.T) {
validations map[string]ValidationFunction
}
type res struct {
want *empty.Empty
want *emptypb.Empty
hasErr bool
}
tests := []struct {
@@ -27,7 +27,7 @@ func TestValidator_Healthz(t *testing.T) {
"ok",
fields{},
res{
&empty.Empty{},
&emptypb.Empty{},
false,
},
},
@@ -37,7 +37,7 @@ func TestValidator_Healthz(t *testing.T) {
v := &Validator{
validations: tt.fields.validations,
}
got, err := v.Healthz(nil, &empty.Empty{})
got, err := v.Healthz(context.Background(), &emptypb.Empty{})
if (err != nil) != tt.res.hasErr {
t.Errorf("Healthz() error = %v, wantErr %v", err, tt.res.hasErr)
return
@@ -54,7 +54,7 @@ func TestValidator_Ready(t *testing.T) {
validations map[string]ValidationFunction
}
type res struct {
want *empty.Empty
want *emptypb.Empty
hasErr bool
}
tests := []struct {
@@ -82,7 +82,7 @@ func TestValidator_Ready(t *testing.T) {
},
}},
res{
&empty.Empty{},
&emptypb.Empty{},
false,
},
},
@@ -92,7 +92,7 @@ func TestValidator_Ready(t *testing.T) {
v := &Validator{
validations: tt.fields.validations,
}
got, err := v.Ready(context.Background(), &empty.Empty{})
got, err := v.Ready(context.Background(), &emptypb.Empty{})
if (err != nil) != tt.res.hasErr {
t.Errorf("Ready() error = %v, wantErr %v", err, tt.res.hasErr)
return
@@ -109,7 +109,7 @@ func Test_validate(t *testing.T) {
validations map[string]ValidationFunction
}
type res struct {
want map[string]error
want map[string]any
}
tests := []struct {
name string
@@ -126,7 +126,7 @@ func Test_validate(t *testing.T) {
},
},
res{
map[string]error{},
map[string]any{},
},
},
{
@@ -142,7 +142,7 @@ func Test_validate(t *testing.T) {
},
},
res{
map[string]error{
map[string]any{
"error": errors.ThrowInternal(nil, "id", "message"),
},
},

View File

@@ -4,6 +4,7 @@ import (
"context"
"net"
"net/http"
"time"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -17,9 +18,20 @@ import (
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
)
var (
timestampComparisons = map[objpb.TimestampQueryMethod]query.TimestampComparison{
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_EQUALS: query.TimestampEquals,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER: query.TimestampGreater,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER_OR_EQUALS: query.TimestampGreaterOrEquals,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS: query.TimestampLess,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS_OR_EQUALS: query.TimestampLessOrEquals,
}
)
func (s *Server) GetSession(ctx context.Context, req *session.GetSessionRequest) (*session.GetSessionResponse, error) {
res, err := s.query.SessionByID(ctx, true, req.GetSessionId(), req.GetSessionToken())
if err != nil {
@@ -46,7 +58,7 @@ func (s *Server) ListSessions(ctx context.Context, req *session.ListSessionsRequ
}
func (s *Server) CreateSession(ctx context.Context, req *session.CreateSessionRequest) (*session.CreateSessionResponse, error) {
checks, metadata, userAgent, err := s.createSessionRequestToCommand(ctx, req)
checks, metadata, userAgent, lifetime, err := s.createSessionRequestToCommand(ctx, req)
if err != nil {
return nil, err
}
@@ -55,7 +67,7 @@ func (s *Server) CreateSession(ctx context.Context, req *session.CreateSessionRe
return nil, err
}
set, err := s.command.CreateSession(ctx, cmds, metadata, userAgent)
set, err := s.command.CreateSession(ctx, cmds, metadata, userAgent, lifetime)
if err != nil {
return nil, err
}
@@ -78,7 +90,7 @@ func (s *Server) SetSession(ctx context.Context, req *session.SetSessionRequest)
return nil, err
}
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), req.GetSessionToken(), cmds, req.GetMetadata())
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), req.GetSessionToken(), cmds, req.GetMetadata(), req.GetLifetime().AsDuration())
if err != nil {
return nil, err
}
@@ -113,13 +125,14 @@ func sessionsToPb(sessions []*query.Session) []*session.Session {
func sessionToPb(s *query.Session) *session.Session {
return &session.Session{
Id: s.ID,
CreationDate: timestamppb.New(s.CreationDate),
ChangeDate: timestamppb.New(s.ChangeDate),
Sequence: s.Sequence,
Factors: factorsToPb(s),
Metadata: s.Metadata,
UserAgent: userAgentToPb(s.UserAgent),
Id: s.ID,
CreationDate: timestamppb.New(s.CreationDate),
ChangeDate: timestamppb.New(s.ChangeDate),
Sequence: s.Sequence,
Factors: factorsToPb(s),
Metadata: s.Metadata,
UserAgent: userAgentToPb(s.UserAgent),
ExpirationDate: expirationToPb(s.Expiration),
}
}
@@ -147,6 +160,13 @@ func userAgentToPb(ua domain.UserAgent) *session.UserAgent {
return out
}
func expirationToPb(expiration time.Time) *timestamppb.Timestamp {
if expiration.IsZero() {
return nil
}
return timestamppb.New(expiration)
}
func factorsToPb(s *query.Session) *session.Factors {
user := userFactorToPb(s.UserFactor)
if user == nil {
@@ -231,9 +251,10 @@ func listSessionsRequestToQuery(ctx context.Context, req *session.ListSessionsRe
}
return &query.SessionsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: fieldNameToSessionColumn(req.GetSortingColumn()),
},
Queries: queries,
}, nil
@@ -241,8 +262,8 @@ func listSessionsRequestToQuery(ctx context.Context, req *session.ListSessionsRe
func sessionQueriesToQuery(ctx context.Context, queries []*session.SearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries)+1)
for i, query := range queries {
q[i], err = sessionQueryToQuery(query)
for i, v := range queries {
q[i], err = sessionQueryToQuery(v)
if err != nil {
return nil, err
}
@@ -255,10 +276,14 @@ func sessionQueriesToQuery(ctx context.Context, queries []*session.SearchQuery)
return q, nil
}
func sessionQueryToQuery(query *session.SearchQuery) (query.SearchQuery, error) {
switch q := query.Query.(type) {
func sessionQueryToQuery(sq *session.SearchQuery) (query.SearchQuery, error) {
switch q := sq.Query.(type) {
case *session.SearchQuery_IdsQuery:
return idsQueryToQuery(q.IdsQuery)
case *session.SearchQuery_UserIdQuery:
return query.NewUserIDSearchQuery(q.UserIdQuery.GetId())
case *session.SearchQuery_CreationDateQuery:
return creationDateQueryToQuery(q.CreationDateQuery)
default:
return nil, caos_errs.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid")
}
@@ -268,12 +293,26 @@ func idsQueryToQuery(q *session.IDsQuery) (query.SearchQuery, error) {
return query.NewSessionIDsSearchQuery(q.Ids)
}
func (s *Server) createSessionRequestToCommand(ctx context.Context, req *session.CreateSessionRequest) ([]command.SessionCommand, map[string][]byte, *domain.UserAgent, error) {
func creationDateQueryToQuery(q *session.CreationDateQuery) (query.SearchQuery, error) {
comparison := timestampComparisons[q.GetMethod()]
return query.NewCreationDateQuery(q.GetCreationDate().AsTime(), comparison)
}
func fieldNameToSessionColumn(field session.SessionFieldName) query.Column {
switch field {
case session.SessionFieldName_SESSION_FIELD_NAME_CREATION_DATE:
return query.SessionColumnCreationDate
default:
return query.Column{}
}
}
func (s *Server) createSessionRequestToCommand(ctx context.Context, req *session.CreateSessionRequest) ([]command.SessionCommand, map[string][]byte, *domain.UserAgent, time.Duration, error) {
checks, err := s.checksToCommand(ctx, req.Checks)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, 0, err
}
return checks, req.GetMetadata(), userAgentToCommand(req.GetUserAgent()), nil
return checks, req.GetMetadata(), userAgentToCommand(req.GetUserAgent()), req.GetLifetime().AsDuration(), nil
}
func userAgentToCommand(userAgent *session.UserAgent) *domain.UserAgent {

View File

@@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@@ -54,7 +55,7 @@ func TestMain(m *testing.M) {
}())
}
func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, window time.Duration, metadata map[string][]byte, userAgent *session.UserAgent, factors ...wantFactor) *session.Session {
func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, window time.Duration, metadata map[string][]byte, userAgent *session.UserAgent, expirationWindow time.Duration, factors ...wantFactor) *session.Session {
t.Helper()
require.NotEmpty(t, id)
require.NotEmpty(t, token)
@@ -75,6 +76,11 @@ func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, windo
if !proto.Equal(userAgent, s.GetUserAgent()) {
t.Errorf("user agent =\n%v\nwant\n%v", s.GetUserAgent(), userAgent)
}
if expirationWindow == 0 {
assert.Nil(t, s.GetExpirationDate())
} else {
assert.WithinRange(t, s.GetExpirationDate().AsTime(), time.Now().Add(-expirationWindow), time.Now().Add(expirationWindow))
}
verifyFactors(t, s.GetFactors(), window, factors)
return s
@@ -137,12 +143,13 @@ func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration,
func TestServer_CreateSession(t *testing.T) {
tests := []struct {
name string
req *session.CreateSessionRequest
want *session.CreateSessionResponse
wantErr bool
wantFactors []wantFactor
wantUserAgent *session.UserAgent
name string
req *session.CreateSessionRequest
want *session.CreateSessionResponse
wantErr bool
wantFactors []wantFactor
wantUserAgent *session.UserAgent
wantExpirationWindow time.Duration
}{
{
name: "empty session",
@@ -182,6 +189,27 @@ func TestServer_CreateSession(t *testing.T) {
},
},
},
{
name: "negative lifetime",
req: &session.CreateSessionRequest{
Metadata: map[string][]byte{"foo": []byte("bar")},
Lifetime: durationpb.New(-5 * time.Minute),
},
wantErr: true,
},
{
name: "lifetime",
req: &session.CreateSessionRequest{
Metadata: map[string][]byte{"foo": []byte("bar")},
Lifetime: durationpb.New(5 * time.Minute),
},
want: &session.CreateSessionResponse{
Details: &object.Details{
ResourceOwner: Tester.Organisation.ID,
},
},
wantExpirationWindow: 5 * time.Minute,
},
{
name: "with user",
req: &session.CreateSessionRequest{
@@ -253,7 +281,7 @@ func TestServer_CreateSession(t *testing.T) {
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
verifyCurrentSession(t, got.GetSessionId(), got.GetSessionToken(), got.GetDetails().GetSequence(), time.Minute, tt.req.GetMetadata(), tt.wantUserAgent, tt.wantFactors...)
verifyCurrentSession(t, got.GetSessionId(), got.GetSessionToken(), got.GetDetails().GetSequence(), time.Minute, tt.req.GetMetadata(), tt.wantUserAgent, tt.wantExpirationWindow, tt.wantFactors...)
})
}
}
@@ -276,7 +304,7 @@ func TestServer_CreateSession_webauthn(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(createResp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
require.NoError(t, err)
@@ -292,7 +320,7 @@ func TestServer_CreateSession_webauthn(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactorUserVerified)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactorUserVerified)
}
func TestServer_CreateSession_successfulIntent(t *testing.T) {
@@ -308,7 +336,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, User.GetUserId(), "id")
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
@@ -322,7 +350,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantIntentFactor)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantIntentFactor)
}
func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
@@ -338,7 +366,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
idpUserID := "id"
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", idpUserID)
@@ -365,7 +393,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantIntentFactor)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantIntentFactor)
}
func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
@@ -381,7 +409,7 @@ func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
intentID := Tester.CreateIntent(t, idpID)
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
@@ -433,7 +461,7 @@ func TestServer_SetSession_flow(t *testing.T) {
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
require.NoError(t, err)
sessionToken := createResp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
t.Run("check user", func(t *testing.T) {
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
@@ -449,7 +477,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor)
})
t.Run("check webauthn, user verified (passkey)", func(t *testing.T) {
@@ -464,7 +492,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
@@ -481,7 +509,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactorUserVerified)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactorUserVerified)
})
userAuthCtx := Tester.WithAuthorizationToken(CTX, sessionToken)
@@ -508,7 +536,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), false)
@@ -525,7 +553,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor)
})
}
})
@@ -544,7 +572,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
})
t.Run("check OTP SMS", func(t *testing.T) {
@@ -556,7 +584,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
otp := resp.GetChallenges().GetOtpSms()
@@ -573,7 +601,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor, wantOTPSMSFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor, wantOTPSMSFactor)
})
t.Run("check OTP Email", func(t *testing.T) {
@@ -587,7 +615,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
otp := resp.GetChallenges().GetOtpEmail()
@@ -604,10 +632,34 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor, wantOTPEmailFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor, wantOTPEmailFactor)
})
}
func TestServer_SetSession_expired(t *testing.T) {
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
Lifetime: durationpb.New(20 * time.Second),
})
require.NoError(t, err)
// test session token works
sessionResp, err := Tester.Client.SessionV2.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: createResp.GetSessionToken(),
Lifetime: durationpb.New(20 * time.Second),
})
require.NoError(t, err)
// ensure session expires and does not work anymore
time.Sleep(20 * time.Second)
_, err = Tester.Client.SessionV2.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
Lifetime: durationpb.New(20 * time.Second),
})
require.Error(t, err)
}
func Test_ZITADEL_API_missing_authentication(t *testing.T) {
// create new, empty session
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
@@ -629,7 +681,7 @@ func Test_ZITADEL_API_missing_mfa(t *testing.T) {
}
func Test_ZITADEL_API_success(t *testing.T) {
id, token, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, User.GetUserId())
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
ctx := Tester.WithAuthorizationToken(context.Background(), token)
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
@@ -641,7 +693,7 @@ func Test_ZITADEL_API_success(t *testing.T) {
}
func Test_ZITADEL_API_session_not_found(t *testing.T) {
id, token, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, User.GetUserId())
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
// test session token works
ctx := Tester.WithAuthorizationToken(context.Background(), token)
@@ -658,3 +710,18 @@ func Test_ZITADEL_API_session_not_found(t *testing.T) {
_, err = Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
require.Error(t, err)
}
func Test_ZITADEL_API_session_expired(t *testing.T) {
id, token, _, _ := Tester.CreateVerifiedWebAuthNSessionWithLifetime(t, CTX, User.GetUserId(), 20*time.Second)
// test session token works
ctx := Tester.WithAuthorizationToken(context.Background(), token)
_, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
require.NoError(t, err)
// ensure session expires and does not work anymore
time.Sleep(20 * time.Second)
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
require.Error(t, err)
require.Nil(t, sessionResp)
}

View File

@@ -14,6 +14,7 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
@@ -22,12 +23,16 @@ import (
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
)
var (
creationDate = time.Date(2023, 10, 10, 14, 15, 0, 0, time.UTC)
)
func Test_sessionsToPb(t *testing.T) {
now := time.Now()
past := now.Add(-time.Hour)
sessions := []*query.Session{
{ // no factor, with user agent
{ // no factor, with user agent and expiration
ID: "999",
CreationDate: now,
ChangeDate: now,
@@ -42,6 +47,7 @@ func Test_sessionsToPb(t *testing.T) {
IP: net.IPv4(1, 2, 3, 4),
Header: http.Header{"foo": []string{"foo", "bar"}},
},
Expiration: now,
},
{ // user factor
ID: "999",
@@ -124,7 +130,7 @@ func Test_sessionsToPb(t *testing.T) {
}
want := []*session.Session{
{ // no factor, with user agent
{ // no factor, with user agent and expiration
Id: "999",
CreationDate: timestamppb.New(now),
ChangeDate: timestamppb.New(now),
@@ -139,6 +145,7 @@ func Test_sessionsToPb(t *testing.T) {
"foo": {Values: []string{"foo", "bar"}},
},
},
ExpirationDate: timestamppb.New(now),
},
{ // user factor
Id: "999",
@@ -307,11 +314,18 @@ func mustNewListQuery(t testing.TB, column query.Column, list []any, compare que
return q
}
func mustNewTimestampQuery(t testing.TB, column query.Column, ts time.Time, compare query.TimestampComparison) query.SearchQuery {
q, err := query.NewTimestampQuery(column, ts, compare)
require.NoError(t, err)
return q
}
func Test_listSessionsRequestToQuery(t *testing.T) {
type args struct {
ctx context.Context
req *session.ListSessionsRequest
}
tests := []struct {
name string
args args
@@ -335,6 +349,26 @@ func Test_listSessionsRequestToQuery(t *testing.T) {
},
},
},
{
name: "default request with sorting column",
args: args{
ctx: authz.NewMockContext("123", "456", "789"),
req: &session.ListSessionsRequest{
SortingColumn: session.SessionFieldName_SESSION_FIELD_NAME_CREATION_DATE,
},
},
want: &query.SessionsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: 0,
Limit: 0,
SortingColumn: query.SessionColumnCreationDate,
Asc: false,
},
Queries: []query.SearchQuery{
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
},
},
},
{
name: "with list query and sessions",
args: args{
@@ -356,6 +390,17 @@ func Test_listSessionsRequestToQuery(t *testing.T) {
Ids: []string{"4", "5", "6"},
},
}},
{Query: &session.SearchQuery_UserIdQuery{
UserIdQuery: &session.UserIDQuery{
Id: "10",
},
}},
{Query: &session.SearchQuery_CreationDateQuery{
CreationDateQuery: &session.CreationDateQuery{
CreationDate: timestamppb.New(creationDate),
Method: objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER,
},
}},
},
},
},
@@ -368,6 +413,8 @@ func Test_listSessionsRequestToQuery(t *testing.T) {
Queries: []query.SearchQuery{
mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
mustNewListQuery(t, query.SessionColumnID, []interface{}{"4", "5", "6"}, query.ListIn),
mustNewTextQuery(t, query.SessionColumnUserID, "10", query.TextEquals),
mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampGreater),
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
},
},
@@ -485,7 +532,7 @@ func Test_sessionQueryToQuery(t *testing.T) {
wantErr: caos_errs.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid"),
},
{
name: "query",
name: "ids query",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_IdsQuery{
IdsQuery: &session.IDsQuery{
@@ -495,6 +542,40 @@ func Test_sessionQueryToQuery(t *testing.T) {
}},
want: mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
},
{
name: "user id query",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_UserIdQuery{
UserIdQuery: &session.UserIDQuery{
Id: "10",
},
},
}},
want: mustNewTextQuery(t, query.SessionColumnUserID, "10", query.TextEquals),
},
{
name: "creation date query",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_CreationDateQuery{
CreationDateQuery: &session.CreationDateQuery{
CreationDate: timestamppb.New(creationDate),
Method: objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS,
},
},
}},
want: mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampLess),
},
{
name: "creation date query with default method",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_CreationDateQuery{
CreationDateQuery: &session.CreationDateQuery{
CreationDate: timestamppb.New(creationDate),
},
},
}},
want: mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampEquals),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -7,10 +7,10 @@ import (
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
)
func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery, err error) {
func UserQueriesToQuery(queries []*user_pb.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = UserQueryToQuery(query)
q[i], err = UserQueryToQuery(query, level)
if err != nil {
return nil, err
}
@@ -18,7 +18,11 @@ func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery,
return q, nil
}
func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
func UserQueryToQuery(query *user_pb.SearchQuery, level uint8) (query.SearchQuery, error) {
if level > 20 {
// can't go deeper than 20 levels of nesting.
return nil, errors.ThrowInvalidArgument(nil, "USER-zsQ97", "Errors.User.TooManyNestingLevels")
}
switch q := query.Query.(type) {
case *user_pb.SearchQuery_UserNameQuery:
return UserNameQueryToQuery(q.UserNameQuery)
@@ -42,6 +46,12 @@ func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
return ResourceOwnerQueryToQuery(q.ResourceOwner)
case *user_pb.SearchQuery_InUserIdsQuery:
return InUserIdsQueryToQuery(q.InUserIdsQuery)
case *user_pb.SearchQuery_OrQuery:
return OrQueryToQuery(q.OrQuery, level)
case *user_pb.SearchQuery_AndQuery:
return AndQueryToQuery(q.AndQuery, level)
case *user_pb.SearchQuery_NotQuery:
return NotQueryToQuery(q.NotQuery, level)
default:
return nil, errors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
@@ -90,3 +100,24 @@ func ResourceOwnerQueryToQuery(q *user_pb.ResourceOwnerQuery) (query.SearchQuery
func InUserIdsQueryToQuery(q *user_pb.InUserIDQuery) (query.SearchQuery, error) {
return query.NewUserInUserIdsSearchQuery(q.UserIds)
}
func OrQueryToQuery(q *user_pb.OrQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
}
func AndQueryToQuery(q *user_pb.AndQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserAndSearchQuery(mappedQueries)
}
func NotQueryToQuery(q *user_pb.NotQuery, level uint8) (query.SearchQuery, error) {
mappedQuery, err := UserQueryToQuery(q.Query, level+1)
if err != nil {
return nil, err
}
return query.NewUserNotSearchQuery(mappedQuery)
}

View File

@@ -16,13 +16,13 @@ import (
func TestServer_AddOTPSMS(t *testing.T) {
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
// TODO: add when phone can be added to user
/*
userIDPhone := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userIDPhone)
_, sessionTokenPhone, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userIDPhone)
_, sessionTokenPhone, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userIDPhone)
*/
type args struct {
ctx context.Context
@@ -99,7 +99,7 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
/*
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
*/
type args struct {
@@ -157,7 +157,7 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
func TestServer_AddOTPEmail(t *testing.T) {
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
userVerified := Tester.CreateHumanUser(CTX)
_, err := Tester.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{
@@ -166,7 +166,7 @@ func TestServer_AddOTPEmail(t *testing.T) {
})
require.NoError(t, err)
Tester.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
type args struct {
ctx context.Context
@@ -238,11 +238,11 @@ func TestServer_AddOTPEmail(t *testing.T) {
func TestServer_RemoveOTPEmail(t *testing.T) {
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
userVerified := Tester.CreateHumanUser(CTX)
Tester.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
userVerifiedCtx := Tester.WithAuthorizationToken(context.Background(), sessionTokenVerified)
_, err := Tester.Client.UserV2.VerifyEmail(userVerifiedCtx, &user.VerifyEmailRequest{
UserId: userVerified.GetUserId(),

View File

@@ -36,7 +36,7 @@ func TestOPStorage_CreateAuthRequest(t *testing.T) {
func TestOPStorage_CreateAccessToken_code(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -75,7 +75,7 @@ func TestOPStorage_CreateAccessToken_code(t *testing.T) {
func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
clientID := createImplicitClient(t)
authRequestID := createAuthRequestImplicit(t, clientID, redirectURIImplicit)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -125,7 +125,7 @@ func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -150,7 +150,7 @@ func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -186,7 +186,7 @@ func TestOPStorage_RevokeToken_access_token(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -229,7 +229,7 @@ func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -266,7 +266,7 @@ func TestOPStorage_RevokeToken_refresh_token(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -309,7 +309,7 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -344,7 +344,7 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.
func TestOPStorage_RevokeToken_invalid_client(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -376,7 +376,7 @@ func TestOPStorage_TerminateSession(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -413,7 +413,7 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -457,7 +457,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{

View File

@@ -21,7 +21,7 @@ import (
func TestOPStorage_SetUserinfoFromToken(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -67,7 +67,7 @@ func TestOPStorage_SetIntrospectionFromToken(t *testing.T) {
scope := []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess}
authRequestID := createAuthRequest(t, app.GetClientId(), redirectURI, scope...)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{

View File

@@ -57,7 +57,7 @@ func TestMain(m *testing.M) {
func Test_ZITADEL_API_missing_audience_scope(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -148,7 +148,7 @@ func Test_ZITADEL_API_missing_mfa(t *testing.T) {
func Test_ZITADEL_API_success(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, zitadelAudienceScope)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -177,7 +177,7 @@ func Test_ZITADEL_API_success(t *testing.T) {
func Test_ZITADEL_API_inactive_access_token(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess, zitadelAudienceScope)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@@ -219,7 +219,7 @@ func Test_ZITADEL_API_terminated_session(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess, zitadelAudienceScope)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{

View File

@@ -109,10 +109,10 @@ func NewServer(
if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
}
provider, err := op.NewForwardedOpenIDProvider(
"",
provider, err := op.NewProvider(
opConfig,
storage,
op.IssuerFromForwardedOrHost("", op.WithIssuerFromCustomHeaders("forwarded", "x-zitadel-forwarded")),
options...,
)
if err != nil {
@@ -120,11 +120,12 @@ func NewServer(
}
server := &Server{
storage: storage,
LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)),
query: query,
fallbackLogger: fallbackLogger,
hashAlg: crypto.NewBCrypt(10), // as we are only verifying in oidc, the cost is already part of the hash string and the config here is irrelevant.
storage: storage,
LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)),
query: query,
fallbackLogger: fallbackLogger,
hashAlg: crypto.NewBCrypt(10), // as we are only verifying in oidc, the cost is already part of the hash string and the config here is irrelevant.
signingKeyAlgorithm: config.SigningKeyAlgorithm,
}
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
server.Handler = op.RegisterLegacyServer(server, op.WithHTTPMiddleware(

View File

@@ -23,8 +23,9 @@ type Server struct {
query *query.Queries
command *command.Commands
fallbackLogger *slog.Logger
hashAlg crypto.HashAlgorithm
fallbackLogger *slog.Logger
hashAlg crypto.HashAlgorithm
signingKeyAlgorithm string
}
func endpoints(endpointConfig *EndpointConfig) op.Endpoints {
@@ -99,7 +100,7 @@ func (s *Server) Discovery(ctx context.Context, r *op.Request[struct{}]) (_ *op.
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
return s.LegacyServer.Discovery(ctx, r)
return op.NewResponse(s.createDiscoveryConfig(ctx)), nil
}
func (s *Server) Keys(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) {
@@ -199,3 +200,34 @@ func (s *Server) EndSession(ctx context.Context, r *op.Request[oidc.EndSessionRe
return s.LegacyServer.EndSession(ctx, r)
}
func (s *Server) createDiscoveryConfig(ctx context.Context) *oidc.DiscoveryConfiguration {
issuer := op.IssuerFromContext(ctx)
return &oidc.DiscoveryConfiguration{
Issuer: issuer,
AuthorizationEndpoint: s.Endpoints().Authorization.Absolute(issuer),
TokenEndpoint: s.Endpoints().Token.Absolute(issuer),
IntrospectionEndpoint: s.Endpoints().Introspection.Absolute(issuer),
UserinfoEndpoint: s.Endpoints().Userinfo.Absolute(issuer),
RevocationEndpoint: s.Endpoints().Revocation.Absolute(issuer),
EndSessionEndpoint: s.Endpoints().EndSession.Absolute(issuer),
JwksURI: s.Endpoints().JwksURI.Absolute(issuer),
DeviceAuthorizationEndpoint: s.Endpoints().DeviceAuthorization.Absolute(issuer),
ScopesSupported: op.Scopes(s.Provider()),
ResponseTypesSupported: op.ResponseTypes(s.Provider()),
GrantTypesSupported: op.GrantTypes(s.Provider()),
SubjectTypesSupported: op.SubjectTypes(s.Provider()),
IDTokenSigningAlgValuesSupported: []string{s.signingKeyAlgorithm},
RequestObjectSigningAlgValuesSupported: op.RequestObjectSigAlgorithms(s.Provider()),
TokenEndpointAuthMethodsSupported: op.AuthMethodsTokenEndpoint(s.Provider()),
TokenEndpointAuthSigningAlgValuesSupported: op.TokenSigAlgorithms(s.Provider()),
IntrospectionEndpointAuthSigningAlgValuesSupported: op.IntrospectionSigAlgorithms(s.Provider()),
IntrospectionEndpointAuthMethodsSupported: op.AuthMethodsIntrospectionEndpoint(s.Provider()),
RevocationEndpointAuthSigningAlgValuesSupported: op.RevocationSigAlgorithms(s.Provider()),
RevocationEndpointAuthMethodsSupported: op.AuthMethodsRevocationEndpoint(s.Provider()),
ClaimsSupported: op.SupportedClaims(s.Provider()),
CodeChallengeMethodsSupported: op.CodeChallengeMethods(s.Provider()),
UILocalesSupported: s.Provider().SupportedUILocales(),
RequestParameterSupported: s.Provider().RequestObjectSupported(),
}
}

View File

@@ -0,0 +1,119 @@
package oidc
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"golang.org/x/text/language"
)
func TestServer_createDiscoveryConfig(t *testing.T) {
type fields struct {
LegacyServer *op.LegacyServer
signingKeyAlgorithm string
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want *oidc.DiscoveryConfiguration
}{
{
"config",
fields{
LegacyServer: op.NewLegacyServer(
func() *op.Provider {
provider, _ := op.NewForwardedOpenIDProvider("path",
&op.Config{
CodeMethodS256: true,
AuthMethodPost: true,
AuthMethodPrivateKeyJWT: true,
GrantTypeRefreshToken: true,
RequestObjectSupported: true,
SupportedUILocales: []language.Tag{language.English, language.German},
},
nil,
)
return provider
}(),
op.Endpoints{
Authorization: op.NewEndpoint("auth"),
Token: op.NewEndpoint("token"),
Introspection: op.NewEndpoint("introspect"),
Userinfo: op.NewEndpoint("userinfo"),
Revocation: op.NewEndpoint("revoke"),
EndSession: op.NewEndpoint("logout"),
JwksURI: op.NewEndpoint("keys"),
DeviceAuthorization: op.NewEndpoint("device"),
},
),
signingKeyAlgorithm: "RS256",
},
args{
ctx: op.ContextWithIssuer(context.Background(), "https://issuer.com"),
},
&oidc.DiscoveryConfiguration{
Issuer: "https://issuer.com",
AuthorizationEndpoint: "https://issuer.com/auth",
TokenEndpoint: "https://issuer.com/token",
IntrospectionEndpoint: "https://issuer.com/introspect",
UserinfoEndpoint: "https://issuer.com/userinfo",
RevocationEndpoint: "https://issuer.com/revoke",
EndSessionEndpoint: "https://issuer.com/logout",
DeviceAuthorizationEndpoint: "https://issuer.com/device",
CheckSessionIframe: "",
JwksURI: "https://issuer.com/keys",
RegistrationEndpoint: "",
ScopesSupported: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopePhone, oidc.ScopeAddress, oidc.ScopeOfflineAccess},
ResponseTypesSupported: []string{string(oidc.ResponseTypeCode), string(oidc.ResponseTypeIDTokenOnly), string(oidc.ResponseTypeIDToken)},
ResponseModesSupported: nil,
GrantTypesSupported: []oidc.GrantType{oidc.GrantTypeCode, oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeBearer},
ACRValuesSupported: nil,
SubjectTypesSupported: []string{"public"},
IDTokenSigningAlgValuesSupported: []string{"RS256"},
IDTokenEncryptionAlgValuesSupported: nil,
IDTokenEncryptionEncValuesSupported: nil,
UserinfoSigningAlgValuesSupported: nil,
UserinfoEncryptionAlgValuesSupported: nil,
UserinfoEncryptionEncValuesSupported: nil,
RequestObjectSigningAlgValuesSupported: []string{"RS256"},
RequestObjectEncryptionAlgValuesSupported: nil,
RequestObjectEncryptionEncValuesSupported: nil,
TokenEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
TokenEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
RevocationEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
RevocationEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
IntrospectionEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodBasic, oidc.AuthMethodPrivateKeyJWT},
IntrospectionEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
DisplayValuesSupported: nil,
ClaimTypesSupported: nil,
ClaimsSupported: []string{"sub", "aud", "exp", "iat", "iss", "auth_time", "nonce", "acr", "amr", "c_hash", "at_hash", "act", "scopes", "client_id", "azp", "preferred_username", "name", "family_name", "given_name", "locale", "email", "email_verified", "phone_number", "phone_number_verified"},
ClaimsParameterSupported: false,
CodeChallengeMethodsSupported: []oidc.CodeChallengeMethod{"S256"},
ServiceDocumentation: "",
ClaimsLocalesSupported: nil,
UILocalesSupported: []language.Tag{language.English, language.German},
RequestParameterSupported: true,
RequestURIParameterSupported: false,
RequireRequestURIRegistration: false,
OPPolicyURI: "",
OPTermsOfServiceURI: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Server{
LegacyServer: tt.fields.LegacyServer,
signingKeyAlgorithm: tt.fields.signingKeyAlgorithm,
}
assert.Equalf(t, tt.want, s.createDiscoveryConfig(tt.args.ctx), "createDiscoveryConfig(%v)", tt.args.ctx)
})
}
}

View File

@@ -1173,8 +1173,9 @@ func mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOpti
}
}
func (l *Login) sessionParamsFromAuthRequest(ctx context.Context, authReq *domain.AuthRequest, identityProviderID string) []any {
params := []any{authReq.AgentID}
func (l *Login) sessionParamsFromAuthRequest(ctx context.Context, authReq *domain.AuthRequest, identityProviderID string) []idp.Parameter {
params := make([]idp.Parameter, 1, 2)
params[0] = idp.UserAgentID(authReq.AgentID)
if authReq.UserID != "" && identityProviderID != "" {
links, err := l.getUserLinks(ctx, authReq.UserID, identityProviderID)
@@ -1183,27 +1184,21 @@ func (l *Login) sessionParamsFromAuthRequest(ctx context.Context, authReq *domai
return params
}
if len(links.Links) == 1 {
return append(params, keyAndValueToAuthURLOpt("login_hint", links.Links[0].ProvidedUsername))
return append(params, idp.LoginHintParam(links.Links[0].ProvidedUsername))
}
}
if authReq.UserName != "" {
return append(params, keyAndValueToAuthURLOpt("login_hint", authReq.UserName))
return append(params, idp.LoginHintParam(authReq.UserName))
}
if authReq.LoginName != "" {
return append(params, keyAndValueToAuthURLOpt("login_hint", authReq.LoginName))
return append(params, idp.LoginHintParam(authReq.LoginName))
}
if authReq.LoginHint != "" {
return append(params, keyAndValueToAuthURLOpt("login_hint", authReq.LoginHint))
return append(params, idp.LoginHintParam(authReq.LoginHint))
}
return params
}
func keyAndValueToAuthURLOpt(key, value string) rp.AuthURLOpt {
return func() []oauth2.AuthCodeOption {
return []oauth2.AuthCodeOption{oauth2.SetAuthURLParam(key, value)}
}
}
func (l *Login) getUserLinks(ctx context.Context, userID, idpID string) (*query.IDPUserLinks, error) {
userIDQuery, err := query.NewIDPUserLinksUserIDSearchQuery(userID)
if err != nil {

View File

@@ -242,6 +242,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Пол
Female: Женски пол
Male: Мъжки
@@ -277,6 +279,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Правила и условия
TosConfirm: Приемам
TosLinkText: TOS
@@ -336,7 +340,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Упълномощаване на устройството
UserCode:

View File

@@ -0,0 +1,487 @@
Login:
Title: Vítejte zpět!
Description: Zadejte své přihlašovací údaje.
TitleLinking: Přihlášení pro propojení uživatele
DescriptionLinking: Zadejte své přihlašovací údaje pro propojení vašeho externího účtu.
LoginNameLabel: Přihlašovací jméno
UsernamePlaceHolder: uživatelské jméno
LoginnamePlaceHolder: uzivatelskejmeno@doména
ExternalUserDescription: Přihlášení s externím účtem.
MustBeMemberOfOrg: Uživatel musí být členem organizace {{.OrgName}}.
RegisterButtonText: Registrovat
NextButtonText: Další
LDAP:
Title: Přihlášení
Description: Zadejte své přihlašovací údaje.
LoginNameLabel: Přihlašovací jméno
PasswordLabel: Heslo
NextButtonText: Další
SelectAccount:
Title: Vyberte účet
Description: Použijte svůj účet
TitleLinking: Vyberte účet pro propojení uživatele
DescriptionLinking: Vyberte účet pro propojení s vaším externím uživatelem.
OtherUser: Jiný uživatel
SessionState0: aktivní
SessionState1: Odhlášen
MustBeMemberOfOrg: Uživatel musí být členem organizace {{.OrgName}}.
Password:
Title: Heslo
Description: Zadejte své přihlašovací údaje.
PasswordLabel: Heslo
MinLength: Minimální délka
HasUppercase: Velké písmeno
HasLowercase: Malé písmeno
HasNumber: Číslo
HasSymbol: Symbol
Confirmation: Potvrzení shody
ResetLinkText: Obnovit heslo
BackButtonText: Zpět
NextButtonText: Další
UsernameChange:
Title: Změna uživatelského jména
Description: Nastavte své nové uživatelské jméno
UsernameLabel: Uživatelské jméno
CancelButtonText: Zrušit
NextButtonText: Další
UsernameChangeDone:
Title: Uživatelské jméno bylo změněno
Description: Vaše uživatelské jméno bylo úspěšně změněno.
NextButtonText: Další
InitPassword:
Title: Nastavit heslo
Description: Obdrželi jste kód, který musíte vložit níže, abyste nastavili své nové heslo.
CodeLabel: Kód
NewPasswordLabel: Nové heslo
NewPasswordConfirmLabel: Potvrzení hesla
ResendButtonText: Znovu poslat kód
NextButtonText: Další
InitPasswordDone:
Title: Heslo nastaveno
Description: Heslo bylo úspěšně nastaveno
NextButtonText: Další
CancelButtonText: Zrušit
InitUser:
Title: Aktivovat uživatele
Description: Ověřte váš e-mail pomocí níže uvedeného kódu a nastavte své heslo.
CodeLabel: Kód
NewPasswordLabel: Nové heslo
NewPasswordConfirm: Potvrzení hesla
NextButtonText: Další
ResendButtonText: Znovu poslat kód
InitUserDone:
Title: Uživatel aktivován
Description: E-mail ověřen a heslo úspěšně nastaveno
NextButtonText: Další
CancelButtonText: Zrušit
InitMFAPrompt:
Title: Nastavení 2-faktorové autentizace
Description: 2-faktorová autentizace vám poskytuje další zabezpečení pro váš uživatelský účet. Tím je zajištěno, že k vašemu účtu máte přístup pouze vy.
Provider0: Aplikace pro ověřování (např. Google/Microsoft Authenticator, Authy)
Provider1: Zařízením závislé (např. FaceID, Windows Hello, Otisk prstu)
Provider3: OTP SMS
Provider4: OTP E-mail
NextButtonText: Další
SkipButtonText: Přeskočit
InitMFAOTP:
Title: 2-Faktorové ověření
Description: Vytvořte svou 2-faktorovou autentizaci. Stáhněte si ověřovací aplikaci, pokud ji ještě nemáte.
OTPDescription: Naskenujte kód svou ověřovací aplikací (např. Google/Microsoft Authenticator, Authy) nebo zkopírujte tajemství a vložte níže vygenerovaný kód.
SecretLabel: Tajemství
CodeLabel: Kód
NextButtonText: Další
CancelButtonText: Zrušit
InitMFAOTPSMS:
Title: 2-Faktorové ověření
DescriptionPhone: Vytvořte svou 2-faktorovou autentizaci. Zadejte své telefonní číslo k jeho ověření.
DescriptionCode: Vytvořte svou 2-faktorovou autentizaci. Zadejte obdržený kód k ověření vašeho telefonního čísla.
PhoneLabel: Telefon
CodeLabel: Kód
EditButtonText: Upravit
ResendButtonText: Znovu poslat kód
NextButtonText: Další
InitMFAU2F:
Title: Přidat bezpečnostní klíč
Description: Bezpečnostní klíč je ověřovací metoda, která může být integrována do vašeho telefonu, používat Bluetooth nebo se přímo zapojit do USB portu vašeho počítače.
TokenNameLabel: Název bezpečnostního klíče / zařízení
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že máte aktuální verzi, nebo použijte jiný (např. Chrome, Safari, Firefox)
RegisterTokenButtonText: Přidat bezpečnostní klíč
ErrorRetry: Zkuste to znovu, vytvořte novou výzvu nebo vyberte jinou metodu.
InitMFADone:
Title: 2-Faktor ověřen
Description: Skvělé! Úspěšně jste nastavili svou 2-faktorovou autentizaci a váš účet je nyní mnohem bezpečnější. Faktor musí být zadán při každém přihlášení.
NextButtonText: Další
CancelButtonText: Zrušit
MFAProvider:
Provider0: Aplikace pro ověřování (např. Google/Microsoft Authenticator, Authy)
Provider1: Zařízením závislé (např. FaceID, Windows Hello, Otisk prstu)
Provider3: OTP SMS
Provider4: OTP E-mail
ChooseOther: nebo vyberte jinou možnost
VerifyMFAOTP:
Title: Ověřte 2-Faktor
Description: Ověřte váš druhý faktor
CodeLabel: Kód
NextButtonText: Další
VerifyOTP:
Title: Ověřte 2-Faktor
Description: Ověřte váš druhý faktor
CodeLabel: Kód
ResendButtonText: Znovu poslat kód
NextButtonText: Další
VerifyMFAU2F:
Title: 2-Faktorové ověření
Description: Ověřte váš 2-Faktor pomocí registrovaného zařízení (např. FaceID, Windows Hello, Otisk prstu)
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že používáte nejnovější verzi, nebo změňte prohlížeč na podporovaný (Chrome, Safari, Firefox)
ErrorRetry: Zkuste to znovu, vytvořte nový požadavek nebo vyberte jinou metodu.
ValidateTokenButtonText: Ověřte 2-Faktor
Passwordless:
Title: Bezheslové přihlášení
Description: Přihlaste se pomocí ověřovacích metod poskytnutých vaším zařízením, jako je FaceID, Windows Hello nebo Otisk prstu.
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že máte aktuální verzi, nebo použijte jiný (např. Chrome, Safari, Firefox).
ErrorRetry: Zkuste to znovu, vytvořte novou výzvu nebo vyberte jinou metodu.
LoginWithPwButtonText: Přihlásit se heslem
ValidateTokenButtonText: Přihlásit se bez hesla
PasswordlessPrompt:
Title: Nastavení bezheslového přihlášení
Description: Chcete nastavit přihlášení bez hesla? (Ověřovací metody vašeho zařízení, jako je FaceID, Windows Hello nebo Otisk prstu)
DescriptionInit: Musíte nastavit přihlášení bez hesla. Použijte odkaz, který jste obdrželi, pro registraci vašeho zařízení.
PasswordlessButtonText: Přihlásit se bez hesla
NextButtonText: Další
SkipButtonText: Přeskočit
PasswordlessRegistration:
Title: Nastavení bezheslového přihlášení
Description: Přidejte své ověření zadáním názvu (např. MůjMobil, MacBook atd.) a poté kliknutím na tlačítko 'Registrovat bez hesla' níže.
TokenNameLabel: Název zařízení
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že máte aktuální verzi, nebo použijte jiný (např. Chrome, Safari, Firefox).
RegisterTokenButtonText: Registrovat bez hesla
ErrorRetry: Zkuste to znovu, vytvořte novou výzvu nebo vyberte jinou metodu.
PasswordlessRegistrationDone:
Title: Nastavení bezheslového přihlášení dokončeno
Description: Zařízení pro přihlášení bez hesla úspěšně přidáno.
DescriptionClose: Nyní můžete toto okno zavřít.
NextButtonText: Další
CancelButtonText: Zrušit
PasswordChange:
Title: Změna hesla
Description: Změňte si heslo. Zadejte své staré a nové heslo.
OldPasswordLabel: Staré heslo
NewPasswordLabel: Nové heslo
NewPasswordConfirmLabel: Potvrzení hesla
CancelButtonText: Zrušit
NextButtonText: Další
Footer: Patička
PasswordChangeDone:
Title: Změna hesla
Description: Vaše heslo bylo úspěšně změněno.
NextButtonText: Další
PasswordResetDone:
Title: Odkaz na resetování hesla odeslán
Description: Pro dokončení změny hesla zkontrolujte váš e-mail a postupujte podle instrukcí.
NextButtonText: Další
EmailVerification:
Title: Ověření e-mailu
Description: Poslali jsme vám e-mail pro ověření vaší adresy. Zadejte kód do níže uvedeného formuláře.
CodeLabel: Kód
NextButtonText: Další
ResendButtonText: Znovu poslat kód
EmailVerificationDone:
Title: Ověření e-mailu
Description: Vaše e-mailová adresa byla úspěšně ověřena.
NextButtonText: Další
CancelButtonText: Zrušit
LoginButtonText: Přihlásit se
RegisterOption:
Title: Možnosti registrace
Description: Vyberte si, jak se chcete zaregistrovat
RegisterUsernamePasswordButtonText: Pomocí uživatelského jména a hesla
ExternalLoginDescription: nebo se zaregistrujte s externím uživatelem
LoginButtonText: Přihlásit se
RegistrationUser:
Title: Registrace
Description: Zadejte své uživatelské údaje. Váš e-mail bude použit jako vaše přihlašovací jméno.
DescriptionOrgRegister: Zadejte své uživatelské údaje.
EmailLabel: E-mail
UsernameLabel: Uživatelské jméno
FirstnameLabel: Křestní jméno
LastnameLabel: Příjmení
LanguageLabel: Jazyk
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Pohlaví
Female: Žena
Male: Muž
Diverse: různorodé / X
PasswordLabel: Heslo
PasswordConfirmLabel: Potvrzení hesla
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím se
PrivacyLinkText: zásadami ochrany osobních údajů
ExternalLogin: nebo se zaregistrujte s externím uživatelem
BackButtonText: Přihlásit se
NextButtonText: Další
ExternalRegistrationUserOverview:
Title: Registrace externího uživatele
Description: Vaše uživatelské údaje byly převzaty z vybraného poskytovatele. Nyní je můžete změnit nebo doplnit.
EmailLabel: E-mail
UsernameLabel: Uživatelské jméno
FirstnameLabel: Křestní jméno
LastnameLabel: Příjmení
NicknameLabel: Přezdívka
PhoneLabel: Telefonní číslo
LanguageLabel: Jazyk
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím se
PrivacyLinkText: zásadami ochrany osobních údajů
ExternalLogin: nebo se zaregistrujte s externím uživatelem
BackButtonText: Zpět
NextButtonText: Uložit
RegistrationOrg:
Title: Registrace organizace
Description: Zadejte název vaší organizace a uživatelské údaje.
OrgNameLabel: Název organizace
EmailLabel: E-mail
UsernameLabel: Uživatelské jméno
FirstnameLabel: Křestní jméno
LastnameLabel: Příjmení
PasswordLabel: Heslo
PasswordConfirmLabel: Potvrzení hesla
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím se
PrivacyLinkText: zásadami ochrany osobních údajů
SaveButtonText: Vytvořit organizaci
LoginSuccess:
Title: Úspěšné přihlášení
AutoRedirectDescription: Budete automaticky přesměrováni zpět do vaší aplikace. Pokud ne, klikněte na tlačítko níže. Poté můžete okno zavřít.
RedirectedDescription: Nyní můžete okno zavřít.
NextButtonText: Další
LogoutDone:
Title: Odhlášení proběhlo úspěšně
Description: Byli jste úspěšně odhlášeni.
LoginButtonText: Přihlásit se
LinkingUsersDone:
Title: Propojení uživatele
Description: Uživatel propojen.
CancelButtonText: Zrušit
NextButtonText: Další
ExternalNotFound:
Title: Externí uživatel nenalezen
Description: Externí uživatel nebyl nalezen. Chcete propojit svého uživatele nebo automaticky zaregistrovat nového?
LinkButtonText: Propojit
AutoRegisterButtonText: Registrovat
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím s
PrivacyLinkText: zásadami ochrany osobních údajů
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autorizace zařízení
UserCode:
Label: Uživatelský kód
Description: Zadejte uživatelský kód zobrazený na zařízení.
ButtonNext: Další
Action:
Description: Povolte přístup zařízení.
GrantDevice: chystáte se povolit zařízení
AccessToScopes: přístup k následujícím rozsahům
Button:
Allow: Povolit
Deny: Zamítnout
Done:
Description: Hotovo.
Approved: Autorizace zařízení schválena. Nyní se můžete vrátit k zařízení.
Denied: Autorizace zařízení zamítnuta. Nyní se můžete vrátit k zařízení.
Footer:
PoweredBy: Provozováno pomocí
Tos: Obchodní podmínky
PrivacyPolicy: Zásady ochrany osobních údajů
Help: Pomoc
SupportEmail: E-mailová podpora
SignIn: Přihlašte se pomocí {{.Provider}}
Errors:
Internal: Došlo k interní chybě
AuthRequest:
NotFound: Authrequest nebyl nalezen
UserAgentNotCorresponding: User Agent neodpovídá
UserAgentNotFound: ID User Agenta nebylo nalezeno
TokenNotFound: Token nebyl nalezen
RequestTypeNotSupported: Typ požadavku není podporován
MissingParameters: Chybějící požadované parametry
User:
NotFound: Uživatel nebyl nalezen
AlreadyExists: Uživatel již existuje
Inactive: Uživatel je neaktivní
NotFoundOnOrg: Uživatel nebyl nalezen ve zvolené organizaci
NotAllowedOrg: Uživatel není členem požadované organizace
NotMatchingUserID: ID uživatele a uživatele v authrequestu se neshodují
UserIDMissing: ID uživatele chybí
Invalid: Neplatná uživatelská data
DomainNotAllowedAsUsername: Doména je již rezervována a nelze ji použít jako uživatelské jméno
NotAllowedToLink: Uživatel nemá povoleno propojení s externím poskytovatelem přihlášení
Profile:
NotFound: Profil nenalezen
NotChanged: Profil nebyl změněn
Empty: Profil je prázdný
FirstNameEmpty: Jméno v profilu je prázdné
LastNameEmpty: Příjmení v profilu je prázdné
IDMissing: Chybí ID profilu
Email:
NotFound: E-mail nenalezen
Invalid: E-mail je neplatný
AlreadyVerified: E-mail je již ověřen
NotChanged: E-mail nebyl změněn
Empty: E-mail je prázdný
IDMissing: Chybí ID e-mailu
Phone:
NotFound: Telefon nenalezen
Invalid: Telefon je neplatný
AlreadyVerified: Telefon již byl ověřen
Empty: Telefon je prázdný
NotChanged: Telefon nebyl změněn
Address:
NotFound: Adresa nenalezena
NotChanged: Adresa nebyla změněna
Username:
AlreadyExists: Uživatelské jméno již je obsazené
Reserved: Uživatelské jméno není volné
Empty: Uživatelské jméno je prázdné
Password:
ConfirmationWrong: Potvrzení hesla je nesprávné
Empty: Heslo je prázdné
Invalid: Heslo je neplatné
InvalidAndLocked: Heslo je neplatné a uživatel je uzamčen, kontaktujte svého správce.
NotChanged: Nové heslo nesmí být stejné jako stávající heslo
UsernameOrPassword:
Invalid: Uživatelské jméno nebo heslo je neplatné
PasswordComplexityPolicy:
NotFound: Zásady složitosti hesla nenalezeny
MinLength: Heslo je příliš krátké
HasLower: Heslo musí obsahovat malé písmeno
HasUpper: Heslo musí obsahovat velké písmeno
HasNumber: Heslo musí obsahovat číslo
HasSymbol: Heslo musí obsahovat symbol
Code:
Expired: Kód vypršel
Invalid: Kód je neplatný
Empty: Kód je prázdný
CryptoCodeNil: Krypto kód je nulový
NotFound: Kód nebyl nalezen
GeneratorAlgNotSupported: Nepodporovaný algoritmus generátoru
EmailVerify:
UserIDEmpty: ID uživatele je prázdné
ExternalData:
CouldNotRead: Externí data nebylo možné správně přečíst
MFA:
NoProviders: Žádní dostupní poskytovatelé vícefaktorového ověřování
OTP:
AlreadyReady: Vícefaktorové OTP (jednorázové heslo) je již nastaveno
NotExisting: Vícefaktorové OTP (jednorázové heslo) neexistuje
InvalidCode: Neplatný kód
NotReady: Vícefaktorové OTP (jednorázové heslo) není připraveno
Locked: Uživatel je uzamčen
SomethingWentWrong: Něco se pokazilo
NotActive: Uživatel není aktivní
ExternalIDP:
IDPTypeNotImplemented: Typ IDP není implementován
NotAllowed: Externí poskytovatel přihlášení není povolen
IDPConfigIDEmpty: ID konfigurace poskytovatele identity je prázdné
ExternalUserIDEmpty: Externí ID uživatele je prázdné
UserDisplayNameEmpty: Zobrazované jméno uživatele je prázdné
NoExternalUserData: Nebyla přijata žádná externí uživatelská data
CreationNotAllowed: Vytvoření nového uživatele není na tomto poskytovateli povoleno
LinkingNotAllowed: Propojení uživatele není na tomto poskytovateli povoleno
GrantRequired: Přihlášení není možné. Uživatel musí mít alespoň jeden oprávnění na aplikaci. Prosím, kontaktujte svého správce.
ProjectRequired: Přihlášení není možné. Organizace uživatele musí být přidělena k projektu. Prosím, kontaktujte svého správce.
IdentityProvider:
InvalidConfig: Konfigurace poskytovatele identity je neplatná
IAM:
LockoutPolicy:
NotExisting: Zásady uzamčení neexistují
Org:
LoginPolicy:
RegistrationNotAllowed: Registrace není povolena
DeviceAuth:
NotExisting: Kód uživatelského zařízení neexistuje
optional: (volitelné)

View File

@@ -245,6 +245,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Geschlecht
Female: weiblich
Male: männlich
@@ -281,6 +283,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
TosConfirm: Ich akzeptiere die
TosLinkText: AGB
@@ -345,7 +349,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Gerätezulassung
UserCode:

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Gender
Female: Female
Male: Male
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Terms and conditions
TosConfirm: I accept the
TosLinkText: TOS
@@ -346,7 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Device Authorization
UserCode:

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Género
Female: Mujer
Male: Hombre
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Términos y condiciones
TosConfirm: Acepto los
TosLinkText: TDS
@@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
Footer:
PoweredBy: Powered By

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Genre
Female: Femme
Male: Homme
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Termes et conditions
TosConfirm: J'accepte les
TosLinkText: TOS
@@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autorisation de l'appareil

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Genere
Female: Femminile
Male: Maschile
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Termini di servizio
TosConfirm: Accetto i
TosLinkText: Termini di servizio
@@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autorizzazione del dispositivo

View File

@@ -238,6 +238,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: 性別
Female: 女性
Male: 男性
@@ -274,6 +276,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: 利用規約
TosConfirm: 私は利用規約を承諾します。
TosLinkText: TOS
@@ -338,6 +342,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: デバイス認証

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Пол
Female: Женски
Male: Машки
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Правила и услови
TosConfirm: Се согласувам со
TosLinkText: правилата за користење
@@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Овластување преку уред

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Płeć
Female: Kobieta
Male: Mężczyzna
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Warunki i zasady
TosConfirm: Akceptuję
TosLinkText: Warunki korzystania
@@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autoryzacja urządzenia

View File

@@ -242,6 +242,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Gênero
Female: Feminino
Male: Masculino
@@ -267,16 +269,19 @@ ExternalRegistrationUserOverview:
NicknameLabel: Apelido
PhoneLabel: Número de telefone
LanguageLabel: Idioma
German: Alemão
English: Inglês
German: Deutsch
English: English
Italian: Italiano
French: Frans
Chinese: Chinês simplificado
Japanese: Japonês
Polish: Polonês
Spanish: Espanhol
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Termos e condições
TosConfirm: Eu aceito os
TosLinkText: termos de serviço
@@ -330,17 +335,20 @@ ExternalNotFound:
TosLinkText: termos de serviço
PrivacyConfirm: Eu aceito a
PrivacyLinkText: política de privacidade
German: Alemão
English: Inglês
German: Deutsch
English: English
Italian: Italiano
French: Frans
Chinese: Chinês simplificado
Polish: Polonês
Japanese: Japonês
Spanish: Espanhol
Bulgarian: Búlgaro
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autorização de dispositivo
UserCode:

View File

@@ -0,0 +1,487 @@
Login:
Title: С возвращением!
Description: Введите свои данные для входа.
TitleLinking: Логин для привязки пользователей
DescriptionLinking: Введите свои данные для входа, чтобы связать внешнего пользователя.
LoginNameLabel: Имя пользователя
UsernamePlaceHolder: Имя пользователя
LoginnamePlaceHolder: username@domain
ExternalUserDescription: Войдите в систему с внешним пользователем.
MustBeMemberOfOrg: Пользователь должен быть членом {{.OrgName}} организации.
RegisterButtonText: регистрировать
NextButtonText: следующий
LDAP:
Title: Войти
Description: Введите ваши данные дял входа.
LoginNameLabel: Имя пользователя
PasswordLabel: Пароль
NextButtonText: следующий
SelectAccount:
Title: Выберите учетную запись
Description: Используйте свой аккаунт
TitleLinking: Выберите учетную запись для привязки пользователя
DescriptionLinking: Выберите свою учетную запись для связи с внешним пользователем.
OtherUser: Другой пользователь
SessionState0: активный
SessionState1: Вышел из системы
MustBeMemberOfOrg: Пользователь должен быть членом {{.OrgName}} организации.
Password:
Title: Пароль
Description: Введите свои данные для входа.
PasswordLabel: Пароль
MinLength: Минимальная длина
HasUppercase: Прописная буква
HasLowercase: Строчная буква
HasNumber: Число
HasSymbol: Символ
Confirmation: Подтверждающее совпадение
ResetLinkText: Сброс пароля
BackButtonText: Назад
NextButtonText: следующий
UsernameChange:
Title: Изменить имя пользователя
Description: Установите новое имя пользователя
UsernameLabel: Имя пользователя
CancelButtonText: Отмена
NextButtonText: следующий
UsernameChangeDone:
Title: Имя пользователя изменено
Description: Ваше имя пользователя было успешно изменено.
NextButtonText: следующий
InitPassword:
Title: Установить пароль
Description: Вы получили код, который необходимо ввести в форму ниже, чтобы установить новый пароль.
CodeLabel: Код
NewPasswordLabel: Новый пароль
NewPasswordConfirmLabel: Подтвердите пароль
ResendButtonText: Повторная отправка кода
NextButtonText: следующий
InitPasswordDone:
Title: Установка пароля
Description: Пароль успешно установлен
NextButtonText: следующий
CancelButtonText: Отмена
InitUser:
Title: Активировать пользователя
Description: Подтвердите свой адрес электронной почты с помощью приведенного ниже кода и установите пароль.
CodeLabel: Код
NewPasswordLabel: Новый пароль
NewPasswordConfirm: Подтвердите пароль
NextButtonText: следующий
ResendButtonText: Повторная отправка кода
InitUserDone:
Title: Пользователь активирован
Description: Адрес электронной почты подтвержден и пароль успешно установлен
NextButtonText: следующий
CancelButtonText: Отмена
InitMFAPrompt:
Title: 2-факторная настройка
Description: 2-факторная аутентификация обеспечивает дополнительную безопасность вашей учетной записи пользователя. Это гарантирует, что только у вас есть доступ к вашей учетной записи.
Provider0: Приложение Authenticator (например, Google/Microsoft Authenticator, Authy)
Provider1: Зависит от устройства (например, FaceID, Windows Hello, отпечаток пальца)
Provider3: OTP SMS
Provider4: Электронная почта OTP
NextButtonText: следующий
SkipButtonText: скип
InitMFAOTP:
Title: 2-факторная верификация
Description: Создайте свой 2-фактор. Загрузите приложение для проверки подлинности, если у вас его еще нет.
OTPDescription: Отсканируйте код с помощью приложения для аутентификации (например, Google/Microsoft Authenticator, Authy) или скопируйте секрет и вставьте сгенерированный код ниже.
SecretLabel: Секрет
CodeLabel: Код
NextButtonText: следующий
CancelButtonText: Отмена
InitMFAOTPSMS:
Title: 2-факторная верификация
DescriptionPhone: Создайте свой 2-фактор. Введите свой номер телефона, чтобы подтвердить его.
DescriptionCode: Создайте свой 2-фактор. Введите полученный код, чтобы подтвердить свой номер телефона.
PhoneLabel: Телефон
CodeLabel: Код
EditButtonText: редактировать
ResendButtonText: Повторная отправка кода
NextButtonText: следующий
InitMFAU2F:
Title: Добавление ключа безопасности
Description: Ключ безопасности — это метод проверки, который можно встроить в телефон, использовать Bluetooth или подключить непосредственно к USB-порту компьютера.
TokenNameLabel: Имя ключа безопасности / устройства
NotSupported: WebAuthN не поддерживается вашим браузером. Пожалуйста, убедитесь, что он обновлен или используйте другой (например, Chrome, Safari, Firefox)
RegisterTokenButtonText: Добавление ключа безопасности
ErrorRetry: Повторите попытку, создайте новую задачу или выберите другой метод.
InitMFADone:
Title: 2-факторная проверка
Description: Замечательно! Вы только что успешно настроили свой 2-фактор и сделали свою учетную запись более безопасной. Фактор должен быть введен при каждом входе в систему.
NextButtonText: следующий
CancelButtonText: Отмена
MFAProvider:
Provider0: Приложение Authenticator (например, Google/Microsoft Authenticator, Authy)
Provider1: Зависит от устройства (например, FaceID, Windows Hello, отпечаток пальца)
Provider3: OTP SMS
Provider4: Электронная почта OTP
ChooseOther: или выберите другой вариант
VerifyMFAOTP:
Title: Проверка 2-фактора
Description: Проверьте свой второй фактор
CodeLabel: Код
NextButtonText: следующий
VerifyOTP:
Title: Проверка 2-фактора
Description: Проверьте свой второй фактор
CodeLabel: Код
ResendButtonText: Повторная отправка кода
NextButtonText: следующий
VerifyMFAU2F:
Title: 2-факторная верификация
Description: Подтвердите свой 2-фактор с зарегистрированным устройством (например, FaceID, Windows Hello, отпечаток пальца)
NotSupported: WebAuthN не поддерживается вашим браузером. Убедитесь, что вы используете последнюю версию или измените браузер на поддерживаемый (Chrome, Safari, Firefox)
ErrorRetry: Повторите попытку, создайте новый запрос или выберите другой метод.
ValidateTokenButtonText: Проверить второй фактор
Passwordless:
Title: Вход без пароля
Description: Войдите в систему с помощью методов аутентификации, предоставляемых вашим устройством, таких как FaceID, Windows Hello или отпечаток пальца.
NotSupported: WebAuthN не поддерживается вашим браузером. Пожалуйста, убедитесь, что он обновлен или используйте другой (например, Chrome, Safari, Firefox)
ErrorRetry: Повторите попытку, создайте новую задачу или выберите другой метод.
LoginWithPwButtonText: Вход с паролем
ValidateTokenButtonText: Вход без пароля
PasswordlessPrompt:
Title: Настройка без пароля
Description: Хотите настроить вход без пароля? (Методы аутентификации вашего устройства, такие как FaceID, Windows Hello или отпечаток пальца)
DescriptionInit: Вам необходимо настроить вход без пароля. Используйте ссылку, которую вам дали, чтобы зарегистрировать свое устройство.
PasswordlessButtonText: Переход без пароля
NextButtonText: следующий
SkipButtonText: скип
PasswordlessRegistration:
Title: Настройка без пароля
Description: Добавьте свою аутентификацию, указав имя (например, MyMobilePhone, MacBook и т. д.), а затем нажав кнопку «Зарегистрироваться без пароля» ниже.
TokenNameLabel: Имя устройства
NotSupported: WebAuthN не поддерживается вашим браузером. Пожалуйста, убедитесь, что он обновлен или используйте другой (например, Chrome, Safari, Firefox)
RegisterTokenButtonText: Регистрация без пароля
ErrorRetry: Повторите попытку, создайте новую задачу или выберите другой метод.
PasswordlessRegistrationDone:
Title: Настройка без пароля
Description: Устройство для работы без пароля успешно добавлено.
DescriptionClose: Теперь вы можете закрыть это окно.
NextButtonText: следующий
CancelButtonText: Отмена
PasswordChange:
Title: Смена пароля
Description: Смените пароль. Введите свой старый и новый пароль.
OldPasswordLabel: Старый пароль
NewPasswordLabel: Новый пароль
NewPasswordConfirmLabel: Подтверждение пароля
CancelButtonText: Отмена
NextButtonText: следующий
Footer: Нижний колонтитул
PasswordChangeDone:
Title: Смена пароля
Description: Ваш пароль был успешно изменен.
NextButtonText: следующий
PasswordResetDone:
Title: Ссылка для сброса пароля отправлена
Description: Проверьте свою электронную почту, чтобы сбросить пароль.
NextButtonText: следующий
EmailVerification:
Title: Проверка электронной почты
Description: Мы отправили вам электронное письмо для подтверждения вашего адреса. Пожалуйста, введите код в форму ниже.
CodeLabel: Код
NextButtonText: следующий
ResendButtonText: Повторная отправка кода
EmailVerificationDone:
Title: Проверка электронной почты
Description: Ваш адрес электронной почты успешно подтвержден.
NextButtonText: следующий
CancelButtonText: Отмена
LoginButtonText: логин
RegisterOption:
Title: Варианты регистрации
Description: Выберите, как вы хотите зарегистрироваться
RegisterUsernamePasswordButtonText: С именем пользователя, паролем
ExternalLoginDescription: или зарегистрироваться у внешнего пользователя
LoginButtonText: логин
RegistrationUser:
Title: Регистрация
Description: Введите свои данные пользователя. Ваш адрес электронной почты будет использоваться в качестве имени пользователя.
DescriptionOrgRegister: Введите свои данные пользователя.
EmailLabel: Отправить по электронной почте
UsernameLabel: Имя пользователя
FirstnameLabel: Имя
LastnameLabel: Фамилия
LanguageLabel: Язык
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Род
Female: Женский
Male: Мужской
Diverse: разное / X
PasswordLabel: Пароль
PasswordConfirmLabel: Подтверждение пароля
TosAndPrivacyLabel: Правила и условия
TosConfirm: Я принимаю
TosLinkText: ТОТ
PrivacyConfirm: Я принимаю
PrivacyLinkText: политика конфиденциальности
ExternalLogin: или зарегистрироваться у внешнего пользователя
BackButtonText: логин
NextButtonText: следующий
ExternalRegistrationUserOverview:
Title: Регистрация внешнего пользователя
Description: Мы взяли ваши пользовательские данные у выбранного провайдера. Теперь вы можете изменить или дополнить их.
EmailLabel: Отправить по электронной почте
UsernameLabel: Имя пользователя
FirstnameLabel: Имя
LastnameLabel: Фамилия
NicknameLabel: Прозвище
PhoneLabel: Номер телефона
LanguageLabel: Язык
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Правила и условия
TosConfirm: Я принимаю
TosLinkText: ТОТ
PrivacyConfirm: Я принимаю
PrivacyLinkText: политика конфиденциальности
ExternalLogin: или зарегистрироваться у внешнего пользователя
BackButtonText: Назад
NextButtonText: спасать
RegistrationOrg:
Title: Регистрация организации
Description: Введите название организации и данные пользователя.
OrgNameLabel: Название организации
EmailLabel: Отправить по электронной почте
UsernameLabel: Имя пользователя
FirstnameLabel: Имя
LastnameLabel: Фамилия
PasswordLabel: Пароль
PasswordConfirmLabel: Подтверждение пароля
TosAndPrivacyLabel: Правила и условия
TosConfirm: Я принимаю
TosLinkText: ТОТ
PrivacyConfirm: Я принимаю
PrivacyLinkText: политика конфиденциальности
SaveButtonText: Создать организацию
LoginSuccess:
Title: Вход в систему выполнен успешно
AutoRedirectDescription: Вы будете автоматически перенаправлены обратно к вашему приложению. Если нет, нажмите на кнопку ниже. После этого вы можете закрыть окно.
RedirectedDescription: Теперь вы можете закрыть это окно.
NextButtonText: следующий
LogoutDone:
Title: Вышел из системы
Description: Вы успешно вышли из системы.
LoginButtonText: логин
LinkingUsersDone:
Title: Юзерлинкинг
Description: Пользовательские ссылки сделаны.
CancelButtonText: Отмена
NextButtonText: следующий
ExternalNotFound:
Title: Внешний пользователь не найден
Description: Внешний пользователь не найден. Вы хотите связать своего пользователя или автоматически зарегистрировать нового.
LinkButtonText: Связь
AutoRegisterButtonText: регистрировать
TosAndPrivacyLabel: Правила и условия
TosConfirm: Я принимаю
TosLinkText: ТОТ
PrivacyConfirm: Я принимаю
PrivacyLinkText: политика конфиденциальности
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Авторизация устройства
UserCode:
Label: Код пользователя
Description: Введите код пользователя, представленный на устройстве.
ButtonNext: следующий
Action:
Description: Предоставьте доступ к устройству.
GrantDevice: Вы собираетесь предоставить устройство
AccessToScopes: Доступ к следующим областям
Button:
Allow: разрешать
Deny: отрицать
Done:
Description: Договорились.
Approved: Авторизация устройства одобрена. Теперь вы можете вернуться к устройству.
Denied: Отказано в авторизации устройства. Теперь вы можете вернуться к устройству.
Footer:
PoweredBy: Руководствовался
Tos: ТОТ
PrivacyPolicy: Политика конфиденциальности
Help: Справка
SupportEmail: Электронная почта службы поддержки
SignIn: Вход с помощью {{.Provider}}
Errors:
Internal: Произошла внутренняя ошибка
AuthRequest:
NotFound: Не удалось найти authrequest
UserAgentNotCorresponding: User Agent не соответствует
UserAgentNotFound: Идентификатор агента пользователя не найден
TokenNotFound: Токен не найден
RequestTypeNotSupported: Тип запроса не поддерживается
MissingParameters: Отсутствуют обязательные параметры
User:
NotFound: Не удалось найти пользователя
AlreadyExists: Пользователь уже существует
Inactive: Пользователь неактивен
NotFoundOnOrg: Не удалось найти пользователя в выбранной организации
NotAllowedOrg: Пользователь не является членом требуемой организации
NotMatchingUserID: Пользователь и пользователь в authrequest не совпадают
UserIDMissing: UserID пуст
Invalid: Неверные пользовательские данные
DomainNotAllowedAsUsername: Домен уже зарезервирован и не может быть использован
NotAllowedToLink: Пользователю не разрешается связываться с внешним провайдером входа в систему
Profile:
NotFound: Профиль не найден
NotChanged: Профиль не изменен
Empty: Профиль пуст
FirstNameEmpty: Имя в профиле пусто
LastNameEmpty: Фамилия в профиле пуста
IDMissing: Отсутствует идентификатор профиля
Email:
NotFound: Электронная почта не найдена
Invalid: Адрес электронной почты недействителен
AlreadyVerified: Электронная почта уже подтверждена
NotChanged: Адрес электронной почты не изменился
Empty: Электронная почта пуста
IDMissing: Отсутствует идентификатор электронной почты
Phone:
NotFound: Телефон не найден
Invalid: Телефон недействителен
AlreadyVerified: Телефон уже проверен
Empty: Телефон пуст
NotChanged: Телефон не менялся
Address:
NotFound: Адрес не найден
NotChanged: Адрес не изменился
Username:
AlreadyExists: Имя пользователя уже занято
Reserved: Имя пользователя уже занято
Empty: Имя пользователя пусто
Password:
ConfirmationWrong: Неверное подтверждение пароля
Empty: Пароль пуст
Invalid: Пароль недействителен
InvalidAndLocked: Пароль недействителен и пользователь заблокирован, обратитесь к администратору.
NotChanged: Пароль не изменен
UsernameOrPassword:
Invalid: Имя пользователя или пароль недействительны
PasswordComplexityPolicy:
NotFound: Политика паролей не найдена
MinLength: Пароль слишком короткий
HasLower: Пароль должен содержать строчную букву
HasUpper: Пароль должен содержать верхнюю букву
HasNumber: Пароль должен содержать цифру
HasSymbol: Пароль должен содержать символ
Code:
Expired: Срок действия кода истек
Invalid: Код недействителен
Empty: Код пустой
CryptoCodeNil: Криптокод равен нулю
NotFound: Не удалось найти код
GeneratorAlgNotSupported: Неподдерживаемый алгоритм генератора
EmailVerify:
UserIDEmpty: UserID пуст
ExternalData:
CouldNotRead: Внешние данные не могут быть прочитаны правильно
MFA:
NoProviders: Нет доступных многофакторных провайдеров
OTP:
AlreadyReady: Одноразовый код-пароль уже настроен
NotExisting: Одноразовый код-пароль не настроен
InvalidCode: Неверный код-пароль
NotReady: Одноразовый код-пароль не готов
Locked: Пользователь заблокирован
SomethingWentWrong: Что-то пошло не так
NotActive: Пользователь не активен
ExternalIDP:
IDPTypeNotImplemented: Тип IDP не реализован
NotAllowed: Внешний поставщик входа в систему не допускается
IDPConfigIDEmpty: Идентификатор поставщика удостоверений пуст
ExternalUserIDEmpty: Идентификатор внешнего пользователя пуст
UserDisplayNameEmpty: Отображаемое имя пользователя пусто
NoExternalUserData: Внешние пользовательские данные не получены
CreationNotAllowed: Создание нового пользователя не допускается на этом провайдере
LinkingNotAllowed: Привязка пользователя к этому провайдеру запрещена
GrantRequired: Вход в систему невозможен. Пользователь должен иметь хотя бы одно разрешение в рамках приложения. Пожалуйста, свяжитесь с вашим администратором.
ProjectRequired: Вход в систему невозможен. Организация пользователя должна быть предоставлена проекту. Обратитесь к администратору.
IdentityProvider:
InvalidConfig: Недопустимая конфигурация поставщика удостоверений
IAM:
LockoutPolicy:
NotExisting: Политика блокировки не существует
Org:
LoginPolicy:
RegistrationNotAllowed: Регистрация не допускается
DeviceAuth:
NotExisting: Код пользователя не существует
optional: (необязательно)

View File

@@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: 性别
Female: 女性
Male: 男性
@@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: 条款和条款
TosConfirm: 我接受
TosLinkText: 服务条款
@@ -346,7 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: 设备授权
UserCode:

View File

@@ -86,6 +86,10 @@
</option>
<option value="mk" id="mk" {{if (selectedLanguage "mk")}} selected {{end}}>{{t "ExternalNotFound.Macedonian"}}
</option>
<option value="cs" id="cs" {{if (selectedLanguage "cs")}} selected {{end}}>{{t "ExternalNotFound.Czech"}}
</option>
<option value="ru" id="mk" {{if (selectedLanguage "ru")}} selected {{end}}>{{t "ExternalNotFound.Russian"}}
</option>
</select>
</div>
</div>