mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 12:57:34 +00:00
Merge branch 'main' into next
This commit is contained in:
@@ -17,6 +17,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
||||
Actions: req.Actions,
|
||||
TokenExchange: req.OidcTokenExchange,
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
|
||||
Actions: featureSourceToFlagPb(&f.Actions),
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +46,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
WebKey: req.WebKey,
|
||||
DebugOIDCParentError: req.DebugOidcParentError,
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +62,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
WebKey: featureSourceToFlagPb(&f.WebKey),
|
||||
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -34,6 +35,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := systemFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -74,6 +76,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -109,6 +115,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -124,6 +134,8 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
DebugOidcParentError: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -134,6 +146,8 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
DebugOIDCParentError: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -178,6 +192,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -221,6 +239,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature_pb.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
@@ -17,6 +17,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
||||
Actions: req.Actions,
|
||||
TokenExchange: req.OidcTokenExchange,
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
|
||||
Actions: featureSourceToFlagPb(&f.Actions),
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +46,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
WebKey: req.WebKey,
|
||||
DebugOIDCParentError: req.DebugOidcParentError,
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +62,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
WebKey: featureSourceToFlagPb(&f.WebKey),
|
||||
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -34,6 +35,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := systemFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -74,6 +76,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -109,6 +115,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -124,6 +134,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -134,6 +145,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -178,6 +190,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -221,6 +237,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature_pb.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/handler"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
@@ -245,11 +246,20 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
||||
}
|
||||
|
||||
// If there is no login client header and no id_token_hint or the id_token_hint does not have a session ID,
|
||||
// do a v1 Terminate session.
|
||||
// do a v1 Terminate session (which terminates all sessions of the user agent, identified by cookie).
|
||||
if endSessionRequest.IDTokenHintClaims == nil || endSessionRequest.IDTokenHintClaims.SessionID == "" {
|
||||
return endSessionRequest.RedirectURI, o.TerminateSession(ctx, endSessionRequest.UserID, endSessionRequest.ClientID)
|
||||
}
|
||||
|
||||
// If the sessionID is prefixed by V1, we also terminate a v1 session.
|
||||
if strings.HasPrefix(endSessionRequest.IDTokenHintClaims.SessionID, handler.IDPrefixV1) {
|
||||
err = o.terminateV1Session(ctx, endSessionRequest.UserID, endSessionRequest.IDTokenHintClaims.SessionID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return endSessionRequest.RedirectURI, nil
|
||||
}
|
||||
|
||||
// terminate the v2 session of the id_token_hint
|
||||
_, err = o.command.TerminateSessionWithoutTokenCheck(ctx, endSessionRequest.IDTokenHintClaims.SessionID)
|
||||
if err != nil {
|
||||
@@ -258,6 +268,30 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
||||
return endSessionRequest.RedirectURI, nil
|
||||
}
|
||||
|
||||
// terminateV1Session terminates "v1" sessions created through the login UI.
|
||||
// Depending on the flag, we either terminate a single session or all of the user agent
|
||||
func (o *OPStorage) terminateV1Session(ctx context.Context, userID, sessionID string) error {
|
||||
ctx = authz.SetCtxData(ctx, authz.CtxData{UserID: userID})
|
||||
// if the flag is active we only terminate the specific session
|
||||
if authz.GetFeatures(ctx).OIDCSingleV1SessionTermination {
|
||||
userAgentID, err := o.repo.UserAgentIDBySessionID(ctx, sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return o.command.HumansSignOut(ctx, userAgentID, []string{userID})
|
||||
}
|
||||
// otherwise we search for all active sessions within the same user agent of the current session id
|
||||
userAgentID, userIDs, err := o.repo.ActiveUserIDsBySessionID(ctx, sessionID)
|
||||
if err != nil {
|
||||
logging.WithError(err).Error("error retrieving user sessions")
|
||||
return err
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return o.command.HumansSignOut(ctx, userAgentID, userIDs)
|
||||
}
|
||||
|
||||
func (o *OPStorage) RevokeToken(ctx context.Context, token, userID, clientID string) (err *oidc.Error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() {
|
||||
@@ -564,6 +598,7 @@ func (s *Server) authResponseToken(authReq *AuthRequest, authorizer op.Authorize
|
||||
domain.TokenReasonAuthRequest,
|
||||
nil,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
authReq.SessionID,
|
||||
)
|
||||
if err != nil {
|
||||
op.AuthRequestError(w, r, authReq, err, authorizer)
|
||||
|
@@ -45,6 +45,7 @@ func (s *Server) ClientCredentialsExchange(ctx context.Context, r *op.ClientRequ
|
||||
domain.TokenReasonClientCredentials,
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -85,6 +85,7 @@ func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.A
|
||||
domain.TokenReasonAuthRequest,
|
||||
nil,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
authReq.SessionID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -298,6 +298,7 @@ func (s *Server) createExchangeAccessToken(
|
||||
reason,
|
||||
actor,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", "", 0, err
|
||||
@@ -342,6 +343,7 @@ func (s *Server) createExchangeJWT(
|
||||
reason,
|
||||
actor,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
"",
|
||||
)
|
||||
accessToken, err = s.createJWT(ctx, client, session, getUserInfo, roleAssertion, getSigner)
|
||||
if err != nil {
|
||||
|
@@ -53,6 +53,7 @@ func (s *Server) JWTProfile(ctx context.Context, r *op.Request[oidc.JWTProfileGr
|
||||
domain.TokenReasonJWTProfile,
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -67,6 +67,7 @@ func (s *Server) refreshTokenV1(ctx context.Context, client *Client, r *op.Clien
|
||||
domain.TokenReasonRefresh,
|
||||
refreshToken.Actor,
|
||||
true,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -162,7 +162,7 @@ func (l *Login) handleDeviceAuthAction(w http.ResponseWriter, r *http.Request) {
|
||||
action := mux.Vars(r)["action"]
|
||||
switch action {
|
||||
case deviceAuthAllowed:
|
||||
_, err = l.command.ApproveDeviceAuth(r.Context(), authDev.DeviceCode, authReq.UserID, authReq.UserOrgID, authReq.UserAuthMethodTypes(), authReq.AuthTime, authReq.PreferredLanguage, authReq.ToUserAgent())
|
||||
_, err = l.command.ApproveDeviceAuth(r.Context(), authDev.DeviceCode, authReq.UserID, authReq.UserOrgID, authReq.UserAuthMethodTypes(), authReq.AuthTime, authReq.PreferredLanguage, authReq.ToUserAgent(), authReq.SessionID)
|
||||
case deviceAuthDenied:
|
||||
_, err = l.command.CancelDeviceAuth(r.Context(), authDev.DeviceCode, domain.DeviceAuthCanceledDenied)
|
||||
default:
|
||||
|
@@ -250,6 +250,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Пол
|
||||
Female: Женски пол
|
||||
Male: Мъжки
|
||||
@@ -289,6 +290,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Правила и условия
|
||||
TosConfirm: Приемам
|
||||
TosLinkText: TOS
|
||||
@@ -357,6 +359,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Упълномощаване на устройството
|
||||
UserCode:
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Pohlaví
|
||||
Female: Žena
|
||||
Male: Muž
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Obchodní podmínky
|
||||
TosConfirm: Souhlasím s
|
||||
TosLinkText: obchodními podmínkami
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Autorizace zařízení
|
||||
UserCode:
|
||||
|
@@ -252,6 +252,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Geschlecht
|
||||
Female: weiblich
|
||||
Male: männlich
|
||||
@@ -292,6 +293,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
|
||||
TosConfirm: Ich akzeptiere die
|
||||
TosLinkText: AGB
|
||||
@@ -366,6 +368,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Gerät verbinden
|
||||
UserCode:
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Gender
|
||||
Female: Female
|
||||
Male: Male
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Terms and conditions
|
||||
TosConfirm: I accept the
|
||||
TosLinkText: TOS
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Device Authorization
|
||||
UserCode:
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Género
|
||||
Female: Mujer
|
||||
Male: Hombre
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Términos y condiciones
|
||||
TosConfirm: Acepto los
|
||||
TosLinkText: TDS
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
Footer:
|
||||
PoweredBy: Powered By
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Genre
|
||||
Female: Femme
|
||||
Male: Homme
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Termes et conditions
|
||||
TosConfirm: J'accepte les
|
||||
TosLinkText: TOS
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorisation de l'appareil
|
||||
|
466
internal/api/ui/login/static/i18n/id.yaml
Normal file
466
internal/api/ui/login/static/i18n/id.yaml
Normal file
@@ -0,0 +1,466 @@
|
||||
Login:
|
||||
Title: Selamat Datang kembali!
|
||||
Description: Masukkan data masuk Anda.
|
||||
TitleLinking: Login untuk menghubungkan pengguna
|
||||
DescriptionLinking: Masukkan data login Anda untuk menghubungkan pengguna eksternal Anda.
|
||||
LoginNameLabel: Nama Masuk
|
||||
UsernamePlaceHolder: nama belakang
|
||||
LoginnamePlaceHolder: nama pengguna@domain
|
||||
ExternalUserDescription: Masuk dengan pengguna eksternal.
|
||||
MustBeMemberOfOrg: 'Pengguna harus menjadi anggota {{.OrgName}} organisasi.'
|
||||
RegisterButtonText: Daftar
|
||||
NextButtonText: Berikutnya
|
||||
LDAP:
|
||||
Title: Login
|
||||
Description: Masukkan data masuk Anda.
|
||||
LoginNameLabel: Nama Masuk
|
||||
PasswordLabel: Kata sandi
|
||||
NextButtonText: Berikutnya
|
||||
SelectAccount:
|
||||
Title: Pilih Akun
|
||||
Description: Gunakan akun Anda
|
||||
TitleLinking: Pilih akun untuk penautan pengguna
|
||||
DescriptionLinking: Pilih akun Anda untuk ditautkan dengan pengguna eksternal Anda.
|
||||
OtherUser: Pengguna Lain
|
||||
SessionState0: aktif
|
||||
SessionState1: Keluar
|
||||
MustBeMemberOfOrg: 'Pengguna harus menjadi anggota {{.OrgName}} organisasi.'
|
||||
Password:
|
||||
Title: Kata sandi
|
||||
Description: Masukkan data masuk Anda.
|
||||
PasswordLabel: Kata sandi
|
||||
MinLength: Setidaknya harus begitu
|
||||
MinLengthp2: karakter panjang.
|
||||
MaxLength: Panjangnya harus kurang dari 70 karakter.
|
||||
HasUppercase: Harus menyertakan huruf besar.
|
||||
HasLowercase: Harus menyertakan huruf kecil.
|
||||
HasNumber: Harus menyertakan nomor.
|
||||
HasSymbol: Harus menyertakan simbol.
|
||||
Confirmation: Konfirmasi kata sandi cocok.
|
||||
ResetLinkText: Atur Ulang Kata Sandi
|
||||
BackButtonText: Kembali
|
||||
NextButtonText: Berikutnya
|
||||
UsernameChange:
|
||||
Title: Ubah Nama Pengguna
|
||||
Description: Tetapkan nama pengguna baru Anda
|
||||
UsernameLabel: Nama belakang
|
||||
CancelButtonText: Membatalkan
|
||||
NextButtonText: Berikutnya
|
||||
UsernameChangeDone:
|
||||
Title: Nama Pengguna Berubah
|
||||
Description: Nama pengguna Anda berhasil diubah.
|
||||
NextButtonText: Berikutnya
|
||||
InitPassword:
|
||||
Title: Tetapkan Kata Sandi
|
||||
Description: Anda telah menerima kode, yang harus Anda masukkan pada formulir di bawah ini, untuk mengatur kata sandi baru Anda.
|
||||
CodeLabel: Kode
|
||||
NewPasswordLabel: Kata Sandi Baru
|
||||
NewPasswordConfirmLabel: Konfirmasi Kata Sandi
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
NextButtonText: Berikutnya
|
||||
InitPasswordDone:
|
||||
Title: Kumpulan Kata Sandi
|
||||
Description: Kata sandi berhasil disetel
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
InitUser:
|
||||
Title: Aktifkan Pengguna
|
||||
Description: Verifikasi email Anda dengan kode di bawah ini dan atur kata sandi Anda.
|
||||
CodeLabel: Kode
|
||||
NewPasswordLabel: Kata Sandi Baru
|
||||
NewPasswordConfirm: Konfirmasi Kata Sandi
|
||||
NextButtonText: Berikutnya
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
InitUserDone:
|
||||
Title: Pengguna Diaktifkan
|
||||
Description: Email terverifikasi dan Kata Sandi berhasil ditetapkan
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
InitMFAPrompt:
|
||||
Title: Pengaturan 2 Faktor
|
||||
Description: Otentikasi 2 faktor memberi Anda keamanan tambahan untuk akun pengguna Anda.
|
||||
Provider0: 'Aplikasi Authenticator (misalnya Google/Microsoft Authenticator, Authy)'
|
||||
Provider1: 'Tergantung pada perangkat (misalnya FaceID, Windows Hello, Fingerprint)'
|
||||
Provider3: SMS OTP
|
||||
Provider4: Email OTP
|
||||
NextButtonText: Berikutnya
|
||||
SkipButtonText: Melewati
|
||||
InitMFAOTP:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: 'Buat 2 faktor Anda. '
|
||||
OTPDescription: Pindai kode dengan aplikasi autentikator Anda (misalnya Google/Microsoft Authenticator, Authy) atau salin rahasianya dan masukkan kode yang dihasilkan di bawah.
|
||||
SecretLabel: Rahasia
|
||||
CodeLabel: Kode
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
InitMFAOTPSMS:
|
||||
Title: Verifikasi 2 Faktor
|
||||
DescriptionPhone: 'Buat 2 faktor Anda. '
|
||||
DescriptionCode: 'Buat 2 faktor Anda. '
|
||||
PhoneLabel: Telepon
|
||||
CodeLabel: Kode
|
||||
EditButtonText: Sunting
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
NextButtonText: Berikutnya
|
||||
InitMFAU2F:
|
||||
Title: Tambahkan Kunci Keamanan
|
||||
Description: Kunci keamanan adalah metode verifikasi yang dapat dipasang di ponsel Anda, menggunakan Bluetooth, atau dicolokkan langsung ke port USB komputer Anda.
|
||||
TokenNameLabel: Nama kunci keamanan/perangkat
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
RegisterTokenButtonText: Tambahkan kunci keamanan
|
||||
ErrorRetry: 'Coba lagi, buat tantangan baru atau pilih metode lain.'
|
||||
InitMFADone:
|
||||
Title: Terverifikasi 2 faktor
|
||||
Description: 'Luar biasa! '
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
MFAProvider:
|
||||
Provider0: 'Aplikasi Authenticator (misalnya Google/Microsoft Authenticator, Authy)'
|
||||
Provider1: 'Tergantung pada perangkat (misalnya FaceID, Windows Hello, Fingerprint)'
|
||||
Provider3: SMS OTP
|
||||
Provider4: Email OTP
|
||||
ChooseOther: atau pilih opsi lain
|
||||
VerifyMFAOTP:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: Verifikasi faktor kedua Anda
|
||||
CodeLabel: Kode
|
||||
NextButtonText: Berikutnya
|
||||
VerifyOTP:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: Verifikasi faktor kedua Anda
|
||||
CodeLabel: Kode
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
NextButtonText: Berikutnya
|
||||
VerifyMFAU2F:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: Verifikasi 2-Faktor Anda dengan perangkat yang terdaftar (misalnya FaceID, Windows Hello, Fingerprint)
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
ErrorRetry: 'Coba lagi, buat permintaan baru atau pilih metode lain.'
|
||||
ValidateTokenButtonText: Verifikasi 2 Faktor
|
||||
Passwordless:
|
||||
Title: Masuk Tanpa Kata Sandi
|
||||
Description: Masuk dengan metode autentikasi yang disediakan oleh perangkat Anda seperti FaceID, Windows Hello, atau Sidik Jari.
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
ErrorRetry: 'Coba lagi, buat tantangan baru atau pilih metode lain.'
|
||||
LoginWithPwButtonText: Masuk dengan kata sandi
|
||||
ValidateTokenButtonText: Masuk dengan tanpa kata sandi
|
||||
PasswordlessPrompt:
|
||||
Title: Pengaturan Tanpa Kata Sandi
|
||||
Description: 'Apakah Anda ingin mengatur login tanpa kata sandi? '
|
||||
DescriptionInit: 'Anda perlu mengatur login tanpa kata sandi. '
|
||||
PasswordlessButtonText: Tanpa kata sandi
|
||||
NextButtonText: Berikutnya
|
||||
SkipButtonText: Melewati
|
||||
PasswordlessRegistration:
|
||||
Title: Pengaturan Tanpa Kata Sandi
|
||||
Description: Tambahkan otentikasi Anda dengan memberikan nama (misalnya MyMobilePhone, MacBook, dll) dan kemudian mengklik tombol 'Daftar tanpa kata sandi' di bawah.
|
||||
TokenNameLabel: Nama perangkat
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
RegisterTokenButtonText: Daftar tanpa kata sandi
|
||||
ErrorRetry: 'Coba lagi, buat tantangan baru atau pilih metode lain.'
|
||||
PasswordlessRegistrationDone:
|
||||
Title: Pengaturan Tanpa Kata Sandi
|
||||
Description: Perangkat tanpa kata sandi berhasil ditambahkan.
|
||||
DescriptionClose: Anda sekarang dapat menutup jendela ini.
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
PasswordChange:
|
||||
Title: Ubah Kata Sandi
|
||||
Description: 'Ubah kata sandi Anda. '
|
||||
ExpiredDescription: 'Kata sandi Anda telah kedaluwarsa dan harus diubah. '
|
||||
OldPasswordLabel: Kata Sandi Lama
|
||||
NewPasswordLabel: Kata Sandi Baru
|
||||
NewPasswordConfirmLabel: Konfirmasi kata sandi
|
||||
CancelButtonText: Membatalkan
|
||||
NextButtonText: Berikutnya
|
||||
Footer: catatan kaki
|
||||
PasswordChangeDone:
|
||||
Title: Ubah Kata Sandi
|
||||
Description: Kata sandi Anda berhasil diubah.
|
||||
NextButtonText: Berikutnya
|
||||
PasswordResetDone:
|
||||
Title: Tautan Reset Kata Sandi Terkirim
|
||||
Description: Periksa email Anda untuk mengatur ulang kata sandi Anda.
|
||||
NextButtonText: Berikutnya
|
||||
EmailVerification:
|
||||
Title: Verifikasi Email
|
||||
Description: 'Kami telah mengirimi Anda email untuk memverifikasi alamat Anda. '
|
||||
CodeLabel: Kode
|
||||
NextButtonText: Berikutnya
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
EmailVerificationDone:
|
||||
Title: Verifikasi Email
|
||||
Description: Alamat email Anda telah berhasil diverifikasi.
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
LoginButtonText: Login
|
||||
RegisterOption:
|
||||
Title: Opsi Pendaftaran
|
||||
Description: Pilih bagaimana Anda ingin mendaftar
|
||||
RegisterUsernamePasswordButtonText: Dengan nama pengguna dan kata sandi
|
||||
ExternalLoginDescription: atau mendaftar dengan pengguna eksternal
|
||||
LoginButtonText: Login
|
||||
RegistrationUser:
|
||||
Title: Pendaftaran
|
||||
Description: 'Masukkan Data Pengguna Anda. '
|
||||
DescriptionOrgRegister: Masukkan Data Pengguna Anda.
|
||||
EmailLabel: E-mail
|
||||
UsernameLabel: Nama belakang
|
||||
FirstnameLabel: Nama yang diberikan
|
||||
LastnameLabel: Nama keluarga
|
||||
LanguageLabel: Bahasa
|
||||
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: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Jenis kelamin
|
||||
Female: Perempuan
|
||||
Male: Pria
|
||||
Diverse: beragam / X
|
||||
PasswordLabel: Kata sandi
|
||||
PasswordConfirmLabel: Konfirmasi kata sandi
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
ExternalLogin: atau mendaftar dengan pengguna eksternal
|
||||
BackButtonText: Login
|
||||
NextButtonText: Berikutnya
|
||||
ExternalRegistrationUserOverview:
|
||||
Title: Registrasi Pengguna Eksternal
|
||||
Description: 'Kami telah mengambil detail pengguna Anda dari penyedia yang dipilih. '
|
||||
EmailLabel: E-mail
|
||||
UsernameLabel: Nama belakang
|
||||
FirstnameLabel: Nama yang diberikan
|
||||
LastnameLabel: Nama keluarga
|
||||
NicknameLabel: Nama panggilan
|
||||
PhoneLabel: Nomor telepon
|
||||
LanguageLabel: Bahasa
|
||||
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: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
ExternalLogin: atau mendaftar dengan pengguna eksternal
|
||||
BackButtonText: tidak
|
||||
NextButtonText: Menyimpan
|
||||
RegistrationOrg:
|
||||
Title: Pendaftaran Organisasi
|
||||
Description: Masukkan nama organisasi dan data pengguna Anda.
|
||||
OrgNameLabel: Nama organisasi
|
||||
EmailLabel: E-mail
|
||||
UsernameLabel: Nama belakang
|
||||
FirstnameLabel: Nama yang diberikan
|
||||
LastnameLabel: Nama keluarga
|
||||
PasswordLabel: Kata sandi
|
||||
PasswordConfirmLabel: Konfirmasi kata sandi
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
SaveButtonText: Buat organisasi
|
||||
LoginSuccess:
|
||||
Title: Masuk Berhasil
|
||||
AutoRedirectDescription: 'Anda akan diarahkan kembali ke aplikasi Anda secara otomatis. '
|
||||
RedirectedDescription: Anda sekarang dapat menutup jendela ini.
|
||||
NextButtonText: Berikutnya
|
||||
LogoutDone:
|
||||
Title: Keluar
|
||||
Description: Anda telah berhasil logout.
|
||||
LoginButtonText: Login
|
||||
LinkingUserPrompt:
|
||||
Title: Pengguna Lama Ditemukan
|
||||
Description: 'Apakah Anda ingin menautkan akun Anda yang ada:'
|
||||
LinkButtonText: Link
|
||||
OtherButtonText: Pilihan lain
|
||||
LinkingUsersDone:
|
||||
Title: Menghubungkan Pengguna
|
||||
Description: Tertaut pengguna.
|
||||
CancelButtonText: Membatalkan
|
||||
NextButtonText: Berikutnya
|
||||
ExternalNotFound:
|
||||
Title: Pengguna Eksternal Tidak Ditemukan
|
||||
Description: 'Pengguna eksternal tidak ditemukan. '
|
||||
LinkButtonText: Link
|
||||
AutoRegisterButtonText: Daftar
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
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: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Otorisasi Perangkat
|
||||
UserCode:
|
||||
Label: Kode Pengguna
|
||||
Description: Masukkan kode pengguna yang disajikan pada perangkat.
|
||||
ButtonNext: Berikutnya
|
||||
Action:
|
||||
Description: Berikan akses perangkat.
|
||||
GrantDevice: Anda akan memberikan perangkat
|
||||
AccessToScopes: akses ke cakupan berikut
|
||||
Button:
|
||||
Allow: Mengizinkan
|
||||
Deny: Membantah
|
||||
Done:
|
||||
Description: Selesai.
|
||||
Approved: 'Otorisasi perangkat disetujui. '
|
||||
Denied: 'Otorisasi perangkat ditolak. '
|
||||
Footer:
|
||||
PoweredBy: Didukung oleh
|
||||
Tos: KL
|
||||
PrivacyPolicy: Kebijakan privasi
|
||||
Help: Membantu
|
||||
SupportEmail: Email Dukungan
|
||||
SignIn: 'Masuk dengan {{.Provider}}'
|
||||
Errors:
|
||||
Internal: Terjadi kesalahan internal
|
||||
AuthRequest:
|
||||
NotFound: Tidak dapat menemukan permintaan autentikasi
|
||||
UserAgentNotCorresponding: Agen Pengguna tidak sesuai
|
||||
UserAgentNotFound: ID Agen Pengguna tidak ditemukan
|
||||
TokenNotFound: Token tidak ditemukan
|
||||
RequestTypeNotSupported: Jenis permintaan tidak didukung
|
||||
MissingParameters: Parameter yang diperlukan tidak ada
|
||||
User:
|
||||
NotFound: Pengguna tidak dapat ditemukan
|
||||
AlreadyExists: Pengguna sudah ada
|
||||
Inactive: Pengguna tidak aktif
|
||||
NotFoundOnOrg: Pengguna tidak dapat ditemukan di organisasi yang dipilih
|
||||
NotAllowedOrg: Pengguna bukan anggota organisasi yang diperlukan
|
||||
NotMatchingUserID: Pengguna dan pengguna dalam permintaan authrequest tidak cocok
|
||||
UserIDMissing: ID Pengguna kosong
|
||||
Invalid: Data pengguna tidak valid
|
||||
DomainNotAllowedAsUsername: Domain sudah dipesan dan tidak dapat digunakan
|
||||
NotAllowedToLink: Pengguna tidak diperbolehkan terhubung dengan penyedia login eksternal
|
||||
Profile:
|
||||
NotFound: Profil tidak ditemukan
|
||||
NotChanged: Profil tidak berubah
|
||||
Empty: Profil kosong
|
||||
FirstNameEmpty: Nama depan di profil kosong
|
||||
LastNameEmpty: Nama keluarga di profil kosong
|
||||
IDMissing: ID Profil tidak ada
|
||||
Email:
|
||||
NotFound: Email tidak ditemukan
|
||||
Invalid: Email tidak valid
|
||||
AlreadyVerified: Email sudah diverifikasi
|
||||
NotChanged: Email tidak diubah
|
||||
Empty: Emailnya kosong
|
||||
IDMissing: ID email tidak ada
|
||||
Phone:
|
||||
NotFound: Telepon tidak ditemukan
|
||||
Invalid: Telepon tidak valid
|
||||
AlreadyVerified: Telepon sudah diverifikasi
|
||||
Empty: Telepon kosong
|
||||
NotChanged: Telepon tidak berubah
|
||||
Address:
|
||||
NotFound: Alamat tidak ditemukan
|
||||
NotChanged: Alamat tidak diubah
|
||||
Username:
|
||||
AlreadyExists: Nama pengguna sudah dipakai
|
||||
Reserved: Nama pengguna sudah dipakai
|
||||
Empty: Nama pengguna kosong
|
||||
Password:
|
||||
ConfirmationWrong: Konfirmasi kata sandi salah
|
||||
Empty: Kata sandi kosong
|
||||
Invalid: Kata sandi tidak valid
|
||||
InvalidAndLocked: Kata sandi tidak valid dan pengguna terkunci, hubungi administrator Anda.
|
||||
NotChanged: Kata sandi baru tidak boleh sama dengan kata sandi Anda saat ini
|
||||
UsernameOrPassword:
|
||||
Invalid: Nama Pengguna atau Kata Sandi tidak valid
|
||||
PasswordComplexityPolicy:
|
||||
NotFound: Kebijakan kata sandi tidak ditemukan
|
||||
MinLength: Kata sandi terlalu pendek
|
||||
HasLower: Kata sandi harus mengandung huruf kecil
|
||||
HasUpper: Kata sandi harus mengandung huruf besar
|
||||
HasNumber: Kata sandi harus berisi nomor
|
||||
HasSymbol: Kata sandi harus mengandung simbol
|
||||
Code:
|
||||
Expired: Kode sudah habis masa berlakunya
|
||||
Invalid: Kode tidak valid
|
||||
Empty: Kode kosong
|
||||
CryptoCodeNil: Kode kripto nihil
|
||||
NotFound: Tidak dapat menemukan kode
|
||||
GeneratorAlgNotSupported: Algoritme generator tidak didukung
|
||||
EmailVerify:
|
||||
UserIDEmpty: ID Pengguna kosong
|
||||
ExternalData:
|
||||
CouldNotRead: Data eksternal tidak dapat dibaca dengan benar
|
||||
MFA:
|
||||
NoProviders: Tidak ada penyedia multifaktor yang tersedia
|
||||
OTP:
|
||||
AlreadyReady: OTP multifaktor (OneTimePassword) sudah disiapkan
|
||||
NotExisting: OTP multifaktor (OneTimePassword) tidak ada
|
||||
InvalidCode: Kode tidak valid
|
||||
NotReady: OTP multifaktor (OneTimePassword) belum siap
|
||||
Locked: Pengguna terkunci
|
||||
SomethingWentWrong: Ada yang tidak beres
|
||||
NotActive: Pengguna tidak aktif
|
||||
ExternalIDP:
|
||||
IDPTypeNotImplemented: Tipe IDP tidak diterapkan
|
||||
NotAllowed: Penyedia Login Eksternal tidak diizinkan
|
||||
IDPConfigIDEmpty: ID Penyedia Identitas kosong
|
||||
ExternalUserIDEmpty: ID Pengguna Eksternal kosong
|
||||
UserDisplayNameEmpty: Nama Tampilan Pengguna kosong
|
||||
NoExternalUserData: Tidak ada Data Pengguna eksternal yang diterima
|
||||
CreationNotAllowed: Pembuatan pengguna baru tidak diperbolehkan pada penyedia ini
|
||||
LinkingNotAllowed: Menautkan pengguna tidak diperbolehkan di penyedia ini
|
||||
NoOptionAllowed: 'Pembuatan tautan tidak diperbolehkan pada penyedia ini. '
|
||||
GrantRequired: 'Masuk tidak dapat dilakukan. '
|
||||
ProjectRequired: 'Masuk tidak dapat dilakukan. '
|
||||
IdentityProvider:
|
||||
InvalidConfig: Konfigurasi Penyedia Identitas tidak valid
|
||||
IAM:
|
||||
LockoutPolicy:
|
||||
NotExisting: Kebijakan Lockout tidak ada
|
||||
Org:
|
||||
LoginPolicy:
|
||||
RegistrationNotAllowed: Pendaftaran tidak diperbolehkan
|
||||
DeviceAuth:
|
||||
NotExisting: Kode Pengguna tidak ada
|
||||
optional: (opsional)
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Genere
|
||||
Female: Femminile
|
||||
Male: Maschile
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Termini di servizio
|
||||
TosConfirm: Accetto i
|
||||
TosLinkText: Termini di servizio
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorizzazione del dispositivo
|
||||
|
@@ -245,6 +245,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: 性別
|
||||
Female: 女性
|
||||
Male: 男性
|
||||
@@ -285,6 +286,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: 利用規約
|
||||
TosConfirm: 私は利用規約を承諾します。
|
||||
TosLinkText: TOS
|
||||
@@ -359,6 +361,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: デバイス認証
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Пол
|
||||
Female: Женски
|
||||
Male: Машки
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Правила и услови
|
||||
TosConfirm: Се согласувам со
|
||||
TosLinkText: правилата за користење
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Овластување преку уред
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Geslacht
|
||||
Female: Vrouw
|
||||
Male: Man
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Nederlands: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Algemene voorwaarden
|
||||
TosConfirm: Ik accepteer de
|
||||
TosLinkText: AV
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Apparaat Autorisatie
|
||||
UserCode:
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Płeć
|
||||
Female: Kobieta
|
||||
Male: Mężczyzna
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Warunki i zasady
|
||||
TosConfirm: Akceptuję
|
||||
TosLinkText: Warunki korzystania
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autoryzacja urządzenia
|
||||
|
@@ -249,6 +249,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Gênero
|
||||
Female: Feminino
|
||||
Male: Masculino
|
||||
@@ -289,6 +290,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Termos e condições
|
||||
TosConfirm: Eu aceito os
|
||||
TosLinkText: termos de serviço
|
||||
@@ -363,6 +365,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorização de dispositivo
|
||||
|
@@ -252,6 +252,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Пол
|
||||
Female: Женский
|
||||
Male: Мужской
|
||||
@@ -292,6 +293,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Условия использования
|
||||
TosConfirm: Я согласен с
|
||||
TosLinkText: Пользовательским соглашением
|
||||
@@ -366,6 +368,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Авторизация устройства
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Kön
|
||||
Female: Man
|
||||
Male: Kvinna
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Användarvillkor
|
||||
TosConfirm: Jag accepterar
|
||||
TosLinkText: Användarvillkoren
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Tillgång från hårdvaruenhet
|
||||
UserCode:
|
||||
|
@@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: 性别
|
||||
Female: 女性
|
||||
Male: 男性
|
||||
@@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: 条款和条款
|
||||
TosConfirm: 我接受
|
||||
TosLinkText: 服务条款
|
||||
@@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: 设备授权
|
||||
UserCode:
|
||||
|
@@ -72,6 +72,8 @@
|
||||
</option>
|
||||
<option value="fr" id="fr" {{if (selectedLanguage "fr")}} selected {{end}}>{{t "ExternalNotFound.French"}}
|
||||
</option>
|
||||
<option value="id" id="id" {{if (selectedLanguage "id")}} selected {{end}}>{{t "ExternalNotFound.Indonesian"}}
|
||||
</option>
|
||||
<option value="it" id="it" {{if (selectedLanguage "it")}} selected {{end}}>{{t "ExternalNotFound.Italian"}}
|
||||
</option>
|
||||
<option value="ja" id="ja" {{if (selectedLanguage "ja")}} selected {{end}}>{{t "ExternalNotFound.Japanese"}}
|
||||
|
@@ -74,8 +74,8 @@ type privacyPolicyProvider interface {
|
||||
}
|
||||
|
||||
type userSessionViewProvider interface {
|
||||
UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error)
|
||||
UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error)
|
||||
UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error)
|
||||
UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error)
|
||||
GetLatestUserSessionSequence(ctx context.Context, instanceID string) (*query.CurrentState, error)
|
||||
}
|
||||
|
||||
@@ -1048,6 +1048,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.SessionID = userSession.ID
|
||||
request.DisplayName = userSession.DisplayName
|
||||
request.AvatarKey = userSession.AvatarKey
|
||||
if user.HumanView != nil && user.HumanView.PreferredLanguage != "" {
|
||||
@@ -1532,7 +1533,7 @@ func userSessionsByUserAgentID(ctx context.Context, provider userSessionViewProv
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
session, err := provider.UserSessionsByAgentID(agentID, instanceID)
|
||||
session, err := provider.UserSessionsByAgentID(ctx, agentID, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1572,7 +1573,7 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
|
||||
OnError(err).
|
||||
Errorf("could not get current sequence for userSessionByIDs")
|
||||
|
||||
session, err := provider.UserSessionByIDs(agentID, user.ID, instanceID)
|
||||
session, err := provider.UserSessionByIDs(ctx, agentID, user.ID, instanceID)
|
||||
if err != nil {
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
|
@@ -34,11 +34,11 @@ var (
|
||||
|
||||
type mockViewNoUserSession struct{}
|
||||
|
||||
func (m *mockViewNoUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewNoUserSession) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "id", "user session not found")
|
||||
}
|
||||
|
||||
func (m *mockViewNoUserSession) UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewNoUserSession) UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ func (m *mockViewNoUserSession) GetLatestUserSessionSequence(ctx context.Context
|
||||
|
||||
type mockViewErrUserSession struct{}
|
||||
|
||||
func (m *mockViewErrUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewErrUserSession) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
return nil, zerrors.ThrowInternal(nil, "id", "internal error")
|
||||
}
|
||||
|
||||
func (m *mockViewErrUserSession) UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewErrUserSession) UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
return nil, zerrors.ThrowInternal(nil, "id", "internal error")
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ type mockUser struct {
|
||||
SessionState domain.UserSessionState
|
||||
}
|
||||
|
||||
func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewUserSession) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
return &user_view_model.UserSessionView{
|
||||
ExternalLoginVerification: sql.NullTime{Time: m.ExternalLoginVerification},
|
||||
PasswordlessVerification: sql.NullTime{Time: m.PasswordlessVerification},
|
||||
@@ -86,7 +86,7 @@ func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_vi
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockViewUserSession) UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewUserSession) UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
sessions := make([]*user_view_model.UserSessionView, len(m.Users))
|
||||
for i, user := range m.Users {
|
||||
sessions[i] = &user_view_model.UserSessionView{
|
||||
|
@@ -28,7 +28,7 @@ func (repo *UserRepo) Health(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error) {
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(agentID, authz.GetInstance(ctx).InstanceID())
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(ctx, agentID, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -41,6 +41,14 @@ func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID s
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserAgentIDBySessionID(ctx context.Context, sessionID string) (string, error) {
|
||||
return repo.View.UserAgentIDBySessionID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
|
||||
}
|
||||
|
||||
func (repo *UserRepo) ActiveUserIDsBySessionID(ctx context.Context, sessionID string) (userAgentID string, userIDs []string, err error) {
|
||||
return repo.View.ActiveUserIDsBySessionID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserEventsByID(ctx context.Context, id string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error) {
|
||||
query, err := usr_view.UserByIDQuery(id, authz.GetInstance(ctx).InstanceID(), changeDate, eventTypes)
|
||||
if err != nil {
|
||||
|
@@ -14,7 +14,7 @@ type UserSessionRepo struct {
|
||||
}
|
||||
|
||||
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) {
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(authz.GetCtxData(ctx).AgentID, authz.GetInstance(ctx).InstanceID())
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(ctx, authz.GetCtxData(ctx).AgentID, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
handler2 "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
query2 "github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
@@ -40,6 +41,7 @@ func Register(ctx context.Context, configs Config, view *view.View, queries *que
|
||||
configs.overwrite("UserSession"),
|
||||
view,
|
||||
queries,
|
||||
id.SonyFlakeGenerator(),
|
||||
))
|
||||
|
||||
projections = append(projections, newToken(ctx,
|
||||
|
@@ -2,12 +2,14 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
query2 "github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
@@ -18,12 +20,15 @@ import (
|
||||
|
||||
const (
|
||||
userSessionTable = "auth.user_sessions"
|
||||
|
||||
IDPrefixV1 = "V1_"
|
||||
)
|
||||
|
||||
type UserSession struct {
|
||||
queries *query2.Queries
|
||||
view *auth_view.View
|
||||
es handler.EventStore
|
||||
queries *query2.Queries
|
||||
view *auth_view.View
|
||||
es handler.EventStore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
|
||||
var _ handler.Projection = (*UserSession)(nil)
|
||||
@@ -33,14 +38,16 @@ func newUserSession(
|
||||
config handler.Config,
|
||||
view *auth_view.View,
|
||||
queries *query2.Queries,
|
||||
idGenerator id.Generator,
|
||||
) *handler.Handler {
|
||||
return handler.NewHandler(
|
||||
ctx,
|
||||
&config,
|
||||
&UserSession{
|
||||
queries: queries,
|
||||
view: view,
|
||||
es: config.Eventstore,
|
||||
queries: queries,
|
||||
view: view,
|
||||
es: config.Eventstore,
|
||||
idGenerator: idGenerator,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -187,7 +194,7 @@ func (s *UserSession) Reducers() []handler.AggregateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
|
||||
func (u *UserSession) sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
|
||||
userAgent, err := agentIDFromSession(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -203,14 +210,34 @@ func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handle
|
||||
}, columns...), nil
|
||||
}
|
||||
|
||||
func (u *UserSession) sessionColumnsActivate(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
|
||||
sessionID, err := u.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessionID = IDPrefixV1 + sessionID
|
||||
columns = slices.Grow(columns, 2)
|
||||
columns = append(columns,
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
handler.NewCol(view_model.UserSessionKeyID,
|
||||
handler.OnlySetValueInCase(userSessionTable, sessionID,
|
||||
handler.ConditionOr(
|
||||
handler.ColumnChangedCondition(userSessionTable, view_model.UserSessionKeyState, domain.UserSessionStateTerminated, domain.UserSessionStateActive),
|
||||
handler.ColumnIsNullCondition(userSessionTable, view_model.UserSessionKeyID),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
return u.sessionColumns(event, columns...)
|
||||
}
|
||||
|
||||
func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err error) {
|
||||
// in case anything needs to be change here check if appendEvent function needs the change as well
|
||||
switch event.Type() {
|
||||
case user.UserV1PasswordCheckSucceededType,
|
||||
user.HumanPasswordCheckSucceededType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -218,9 +245,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
|
||||
case user.UserV1PasswordCheckFailedType,
|
||||
user.HumanPasswordCheckFailedType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -228,10 +254,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
|
||||
case user.UserV1MFAOTPCheckSucceededType,
|
||||
user.HumanMFAOTPCheckSucceededType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -240,9 +265,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
case user.UserV1MFAOTPCheckFailedType,
|
||||
user.HumanMFAOTPCheckFailedType,
|
||||
user.HumanU2FTokenCheckFailedType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -250,7 +274,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
|
||||
case user.UserV1SignedOutType,
|
||||
user.HumanSignedOutType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumns(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
|
||||
@@ -270,10 +294,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -285,10 +308,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -300,11 +322,10 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFATypeU2FUserVerification),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -316,10 +337,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -429,8 +449,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
},
|
||||
), nil
|
||||
case user.HumanRegisteredType:
|
||||
columns, err := sessionColumns(event,
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
|
||||
)
|
||||
if err != nil {
|
||||
|
@@ -12,12 +12,20 @@ const (
|
||||
userSessionTable = "auth.user_sessions"
|
||||
)
|
||||
|
||||
func (v *View) UserSessionByIDs(agentID, userID, instanceID string) (*model.UserSessionView, error) {
|
||||
return view.UserSessionByIDs(v.client, agentID, userID, instanceID)
|
||||
func (v *View) UserSessionByIDs(ctx context.Context, agentID, userID, instanceID string) (*model.UserSessionView, error) {
|
||||
return view.UserSessionByIDs(ctx, v.client, agentID, userID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) UserSessionsByAgentID(agentID, instanceID string) ([]*model.UserSessionView, error) {
|
||||
return view.UserSessionsByAgentID(v.client, agentID, instanceID)
|
||||
func (v *View) UserSessionsByAgentID(ctx context.Context, agentID, instanceID string) ([]*model.UserSessionView, error) {
|
||||
return view.UserSessionsByAgentID(ctx, v.client, agentID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) UserAgentIDBySessionID(ctx context.Context, sessionID, instanceID string) (string, error) {
|
||||
return view.UserAgentIDBySessionID(ctx, v.client, sessionID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) ActiveUserIDsBySessionID(ctx context.Context, sessionID, instanceID string) (userAgentID string, userIDs []string, err error) {
|
||||
return view.ActiveUserIDsBySessionID(ctx, v.client, sessionID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
|
||||
|
@@ -6,4 +6,6 @@ import (
|
||||
|
||||
type UserRepository interface {
|
||||
UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error)
|
||||
UserAgentIDBySessionID(ctx context.Context, sessionID string) (string, error)
|
||||
ActiveUserIDsBySessionID(ctx context.Context, sessionID string) (userAgentID string, userIDs []string, err error)
|
||||
}
|
||||
|
@@ -50,6 +50,7 @@ func (c *Commands) ApproveDeviceAuth(
|
||||
authTime time.Time,
|
||||
preferredLanguage *language.Tag,
|
||||
userAgent *domain.UserAgent,
|
||||
sessionID string,
|
||||
) (*domain.ObjectDetails, error) {
|
||||
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode)
|
||||
if err != nil {
|
||||
@@ -58,7 +59,7 @@ func (c *Commands) ApproveDeviceAuth(
|
||||
if !model.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent))
|
||||
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent, sessionID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -151,7 +152,7 @@ func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCo
|
||||
cmd.AddSession(ctx,
|
||||
deviceAuthModel.UserID,
|
||||
deviceAuthModel.UserOrgID,
|
||||
"",
|
||||
deviceAuthModel.SessionID,
|
||||
deviceAuthModel.ClientID,
|
||||
deviceAuthModel.Audience,
|
||||
deviceAuthModel.Scopes,
|
||||
|
@@ -28,6 +28,7 @@ type DeviceAuthWriteModel struct {
|
||||
PreferredLanguage *language.Tag
|
||||
UserAgent *domain.UserAgent
|
||||
NeedRefreshToken bool
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func NewDeviceAuthWriteModel(deviceCode, resourceOwner string) *DeviceAuthWriteModel {
|
||||
@@ -60,6 +61,7 @@ func (m *DeviceAuthWriteModel) Reduce() error {
|
||||
m.AuthTime = e.AuthTime
|
||||
m.PreferredLanguage = e.PreferredLanguage
|
||||
m.UserAgent = e.UserAgent
|
||||
m.SessionID = e.SessionID
|
||||
case *deviceauth.CanceledEvent:
|
||||
m.State = e.Reason.State()
|
||||
case *deviceauth.DoneEvent:
|
||||
|
@@ -137,6 +137,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
authTime time.Time
|
||||
preferredLanguage *language.Tag
|
||||
userAgent *domain.UserAgent
|
||||
sessionID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -161,6 +162,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
},
|
||||
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound"),
|
||||
},
|
||||
@@ -188,6 +190,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -201,6 +204,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
},
|
||||
wantErr: pushErr,
|
||||
},
|
||||
@@ -228,6 +232,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -241,6 +246,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
},
|
||||
wantDetails: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
@@ -252,7 +258,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent)
|
||||
gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent, tt.args.sessionID)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assertObjectDetails(t, tt.wantDetails, gotDetails)
|
||||
})
|
||||
@@ -607,13 +613,14 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
@@ -657,7 +664,8 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
SessionID: "sessionID",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -687,13 +695,14 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
@@ -742,6 +751,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
},
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID
|
||||
SessionID: "sessionID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ type InstanceFeatures struct {
|
||||
ImprovedPerformance []feature.ImprovedPerformanceType
|
||||
WebKey *bool
|
||||
DebugOIDCParentError *bool
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
}
|
||||
|
||||
func (m *InstanceFeatures) isEmpty() bool {
|
||||
@@ -37,7 +38,8 @@ func (m *InstanceFeatures) isEmpty() bool {
|
||||
// nil check to allow unset improvements
|
||||
m.ImprovedPerformance == nil &&
|
||||
m.WebKey == nil &&
|
||||
m.DebugOIDCParentError == nil
|
||||
m.DebugOIDCParentError == nil &&
|
||||
m.OIDCSingleV1SessionTermination == nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
|
||||
|
@@ -69,6 +69,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceImprovedPerformanceEventType,
|
||||
feature_v2.InstanceWebKeyEventType,
|
||||
feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -108,6 +109,9 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
|
||||
case feature.KeyDebugOIDCParentError:
|
||||
v := value.(bool)
|
||||
features.DebugOIDCParentError = &v
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
v := value.(bool)
|
||||
features.OIDCSingleV1SessionTermination = &v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,5 +127,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
|
||||
cmds = appendFeatureSliceUpdate(ctx, cmds, aggregate, wm.ImprovedPerformance, f.ImprovedPerformance, feature_v2.InstanceImprovedPerformanceEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.WebKey, f.WebKey, feature_v2.InstanceWebKeyEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DebugOIDCParentError, f.DebugOIDCParentError, feature_v2.InstanceDebugOIDCParentErrorEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.InstanceOIDCSingleV1SessionTerminationEventType)
|
||||
return cmds
|
||||
}
|
||||
|
@@ -208,6 +208,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceActionsEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{ctx, &InstanceFeatures{
|
||||
@@ -216,6 +220,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
Actions: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
@@ -246,6 +251,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, true,
|
||||
)),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, false,
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
@@ -262,6 +271,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(false),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
|
@@ -136,6 +136,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
reason domain.TokenReason,
|
||||
actor *domain.TokenActor,
|
||||
needRefreshToken bool,
|
||||
sessionID string,
|
||||
) (session *OIDCSession, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -151,7 +152,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
cmd.UserImpersonated(ctx, userID, resourceOwner, clientID, actor)
|
||||
}
|
||||
|
||||
cmd.AddSession(ctx, userID, resourceOwner, "", clientID, audience, scope, authMethods, authTime, nonce, preferredLanguage, userAgent)
|
||||
cmd.AddSession(ctx, userID, resourceOwner, sessionID, clientID, audience, scope, authMethods, authTime, nonce, preferredLanguage, userAgent)
|
||||
if err = cmd.AddAccessToken(ctx, scope, userID, resourceOwner, reason, actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -479,6 +479,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
reason domain.TokenReason
|
||||
actor *domain.TokenActor
|
||||
needRefreshToken bool
|
||||
sessionID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -684,6 +685,89 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with sessionID",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
),
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(),
|
||||
&oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"at_accessTokenID", []string{"openid", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest,
|
||||
&domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
),
|
||||
user.NewUserTokenV2AddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "at_accessTokenID"),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID", "accessTokenID"),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
clientID: "clientID",
|
||||
audience: []string{"audience"},
|
||||
scope: []string{"openid", "offline_access"},
|
||||
authMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
authTime: testNow,
|
||||
nonce: "nonce",
|
||||
preferredLanguage: &language.Afrikaans,
|
||||
userAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
reason: domain.TokenReasonAuthRequest,
|
||||
actor: &domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
sessionID: "sessionID",
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
ClientID: "clientID",
|
||||
UserID: "userID",
|
||||
Audience: []string{"audience"},
|
||||
Expiration: time.Time{}.Add(time.Hour),
|
||||
Scope: []string{"openid", "offline_access"},
|
||||
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
AuthTime: testNow,
|
||||
Nonce: "nonce",
|
||||
PreferredLanguage: &language.Afrikaans,
|
||||
UserAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
Actor: &domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
SessionID: "sessionID",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "impersonation not allowed",
|
||||
fields: fields{
|
||||
@@ -839,6 +923,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
tt.args.reason,
|
||||
tt.args.actor,
|
||||
tt.args.needRefreshToken,
|
||||
tt.args.sessionID,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if got != nil {
|
||||
|
@@ -17,6 +17,7 @@ type SystemFeatures struct {
|
||||
UserSchema *bool
|
||||
Actions *bool
|
||||
ImprovedPerformance []feature.ImprovedPerformanceType
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
}
|
||||
|
||||
func (m *SystemFeatures) isEmpty() bool {
|
||||
@@ -27,7 +28,8 @@ func (m *SystemFeatures) isEmpty() bool {
|
||||
m.TokenExchange == nil &&
|
||||
m.Actions == nil &&
|
||||
// nil check to allow unset improvements
|
||||
m.ImprovedPerformance == nil
|
||||
m.ImprovedPerformance == nil &&
|
||||
m.OIDCSingleV1SessionTermination == nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
|
||||
|
@@ -60,6 +60,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemTokenExchangeEventType,
|
||||
feature_v2.SystemActionsEventType,
|
||||
feature_v2.SystemImprovedPerformanceEventType,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -92,6 +93,9 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
|
||||
features.Actions = &v
|
||||
case feature.KeyImprovedPerformance:
|
||||
features.ImprovedPerformance = value.([]feature.ImprovedPerformanceType)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
v := value.(bool)
|
||||
features.OIDCSingleV1SessionTermination = &v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +109,7 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.TokenExchange, f.TokenExchange, feature_v2.SystemTokenExchangeEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.Actions, f.Actions, feature_v2.SystemActionsEventType)
|
||||
cmds = appendFeatureSliceUpdate(ctx, cmds, aggregate, wm.ImprovedPerformance, f.ImprovedPerformance, feature_v2.SystemImprovedPerformanceEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.SystemOIDCSingleV1SessionTerminationEventType)
|
||||
return cmds
|
||||
}
|
||||
|
||||
|
@@ -176,6 +176,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemActionsEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
@@ -184,6 +188,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
Actions: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
@@ -232,6 +237,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemActionsEventType, false,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, false,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
@@ -240,6 +249,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
Actions: gu.Ptr(false),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(false),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
|
@@ -62,6 +62,8 @@ type AuthRequest struct {
|
||||
SAMLRequestID string
|
||||
// orgID the policies were last loaded with
|
||||
policyOrgID string
|
||||
// SessionID is set to the computed sessionID of the login session table
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func (a *AuthRequest) SetPolicyOrgID(id string) {
|
||||
|
@@ -146,18 +146,17 @@ func NewUpsertStatement(event eventstore.Event, conflictCols []Column, values []
|
||||
conflictTarget[i] = col.Name
|
||||
}
|
||||
|
||||
config := execConfig{
|
||||
args: args,
|
||||
}
|
||||
config := execConfig{}
|
||||
|
||||
if len(values) == 0 {
|
||||
config.err = ErrNoValues
|
||||
}
|
||||
|
||||
updateCols, updateVals := getUpdateCols(values, conflictTarget)
|
||||
updateCols, updateVals, args := getUpdateCols(values, conflictTarget, params, args)
|
||||
if len(updateCols) == 0 || len(updateVals) == 0 {
|
||||
config.err = ErrNoValues
|
||||
}
|
||||
config.args = args
|
||||
|
||||
q := func(config execConfig) string {
|
||||
var updateStmt string
|
||||
@@ -194,18 +193,78 @@ func OnlySetValueOnInsert(table string, value interface{}) *onlySetValueOnInsert
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateCols(cols []Column, conflictTarget []string) (updateCols, updateVals []string) {
|
||||
type onlySetValueInCase struct {
|
||||
Table string
|
||||
Value interface{}
|
||||
Condition Condition
|
||||
}
|
||||
|
||||
func (c *onlySetValueInCase) GetValue() interface{} {
|
||||
return c.Value
|
||||
}
|
||||
|
||||
// ColumnChangedCondition checks the current value and if it changed to a specific new value
|
||||
func ColumnChangedCondition(table, column string, currentValue, newValue interface{}) Condition {
|
||||
return func(param string) (string, []any) {
|
||||
index, _ := strconv.Atoi(param)
|
||||
return fmt.Sprintf("%[1]s.%[2]s = $%[3]d AND EXCLUDED.%[2]s = $%[4]d", table, column, index, index+1), []any{currentValue, newValue}
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnIsNullCondition checks if the current value is null
|
||||
func ColumnIsNullCondition(table, column string) Condition {
|
||||
return func(param string) (string, []any) {
|
||||
return fmt.Sprintf("%[1]s.%[2]s IS NULL", table, column), nil
|
||||
}
|
||||
}
|
||||
|
||||
// ConditionOr links multiple Conditions by OR
|
||||
func ConditionOr(conditions ...Condition) Condition {
|
||||
return func(param string) (_ string, args []any) {
|
||||
if len(conditions) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
b := strings.Builder{}
|
||||
s, arg := conditions[0](param)
|
||||
b.WriteString(s)
|
||||
args = append(args, arg...)
|
||||
for i := 1; i < len(conditions); i++ {
|
||||
b.WriteString(" OR ")
|
||||
s, condArgs := conditions[i](param)
|
||||
b.WriteString(s)
|
||||
args = append(args, condArgs...)
|
||||
}
|
||||
return b.String(), args
|
||||
}
|
||||
}
|
||||
|
||||
// OnlySetValueInCase will only update to the desired value if the condition applies
|
||||
func OnlySetValueInCase(table string, value interface{}, condition Condition) *onlySetValueInCase {
|
||||
return &onlySetValueInCase{
|
||||
Table: table,
|
||||
Value: value,
|
||||
Condition: condition,
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateCols(cols []Column, conflictTarget, params []string, args []interface{}) (updateCols, updateVals []string, updatedArgs []interface{}) {
|
||||
updateCols = make([]string, len(cols))
|
||||
updateVals = make([]string, len(cols))
|
||||
updatedArgs = args
|
||||
|
||||
for i := len(cols) - 1; i >= 0; i-- {
|
||||
col := cols[i]
|
||||
table := "EXCLUDED"
|
||||
if onlyOnInsert, ok := col.Value.(*onlySetValueOnInsert); ok {
|
||||
table = onlyOnInsert.Table
|
||||
}
|
||||
updateCols[i] = col.Name
|
||||
updateVals[i] = table + "." + col.Name
|
||||
switch v := col.Value.(type) {
|
||||
case *onlySetValueOnInsert:
|
||||
updateVals[i] = v.Table + "." + col.Name
|
||||
case *onlySetValueInCase:
|
||||
s, condArgs := v.Condition(strconv.Itoa(len(params) + 1))
|
||||
updatedArgs = append(updatedArgs, condArgs...)
|
||||
updateVals[i] = fmt.Sprintf("CASE WHEN %[1]s THEN EXCLUDED.%[2]s ELSE %[3]s.%[2]s END", s, col.Name, v.Table)
|
||||
default:
|
||||
updateVals[i] = "EXCLUDED" + "." + col.Name
|
||||
}
|
||||
for _, conflict := range conflictTarget {
|
||||
if conflict == col.Name {
|
||||
copy(updateCols[i:], updateCols[i+1:])
|
||||
@@ -221,7 +280,7 @@ func getUpdateCols(cols []Column, conflictTarget []string) (updateCols, updateVa
|
||||
}
|
||||
}
|
||||
|
||||
return updateCols, updateVals
|
||||
return updateCols, updateVals, updatedArgs
|
||||
}
|
||||
|
||||
func NewUpdateStatement(event eventstore.Event, values []Column, conditions []Condition, opts ...execOption) *Statement {
|
||||
|
@@ -451,6 +451,55 @@ func TestNewUpsertStatement(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct all *onlySetValueInCase",
|
||||
args: args{
|
||||
table: "my_table",
|
||||
event: &testEvent{
|
||||
aggregateType: "agg",
|
||||
sequence: 1,
|
||||
previousSequence: 0,
|
||||
},
|
||||
conflictCols: []Column{
|
||||
NewCol("col1", nil),
|
||||
},
|
||||
values: []Column{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: "val1",
|
||||
},
|
||||
{
|
||||
Name: "col2",
|
||||
Value: &onlySetValueInCase{
|
||||
Table: "some.table",
|
||||
Value: "val2",
|
||||
Condition: ConditionOr(
|
||||
ColumnChangedCondition("some.table", "val3", 0, 1),
|
||||
ColumnIsNullCondition("some.table", "val3"),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
table: "my_table",
|
||||
aggregateType: "agg",
|
||||
sequence: 1,
|
||||
previousSequence: 1,
|
||||
executer: &wantExecuter{
|
||||
params: []params{
|
||||
{
|
||||
query: "INSERT INTO my_table (col1, col2) VALUES ($1, $2) ON CONFLICT (col1) DO UPDATE SET col2 = CASE WHEN some.table.val3 = $3 AND EXCLUDED.val3 = $4 OR some.table.val3 IS NULL THEN EXCLUDED.col2 ELSE some.table.col2 END",
|
||||
args: []interface{}{"val1", "val2", 0, 1},
|
||||
},
|
||||
},
|
||||
shouldExecute: true,
|
||||
},
|
||||
isErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@@ -16,6 +16,7 @@ const (
|
||||
KeyImprovedPerformance
|
||||
KeyWebKey
|
||||
KeyDebugOIDCParentError
|
||||
KeyOIDCSingleV1SessionTermination
|
||||
)
|
||||
|
||||
//go:generate enumer -type Level -transform snake -trimprefix Level
|
||||
@@ -41,6 +42,7 @@ type Features struct {
|
||||
ImprovedPerformance []ImprovedPerformanceType `json:"improved_performance,omitempty"`
|
||||
WebKey bool `json:"web_key,omitempty"`
|
||||
DebugOIDCParentError bool `json:"debug_oidc_parent_error,omitempty"`
|
||||
OIDCSingleV1SessionTermination bool `json:"terminate_single_v1_session,omitempty"`
|
||||
}
|
||||
|
||||
type ImprovedPerformanceType int32
|
||||
|
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_error"
|
||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_errorterminate_single_v1_session"
|
||||
|
||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113, 133, 140, 163}
|
||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113, 133, 140, 163, 190}
|
||||
|
||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_error"
|
||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_errorterminate_single_v1_session"
|
||||
|
||||
func (i Key) String() string {
|
||||
if i < 0 || i >= Key(len(_KeyIndex)-1) {
|
||||
@@ -34,9 +34,10 @@ func _KeyNoOp() {
|
||||
_ = x[KeyImprovedPerformance-(7)]
|
||||
_ = x[KeyWebKey-(8)]
|
||||
_ = x[KeyDebugOIDCParentError-(9)]
|
||||
_ = x[KeyOIDCSingleV1SessionTermination-(10)]
|
||||
}
|
||||
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions, KeyImprovedPerformance, KeyWebKey, KeyDebugOIDCParentError}
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions, KeyImprovedPerformance, KeyWebKey, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination}
|
||||
|
||||
var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyName[0:11]: KeyUnspecified,
|
||||
@@ -59,6 +60,8 @@ var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyLowerName[133:140]: KeyWebKey,
|
||||
_KeyName[140:163]: KeyDebugOIDCParentError,
|
||||
_KeyLowerName[140:163]: KeyDebugOIDCParentError,
|
||||
_KeyName[163:190]: KeyOIDCSingleV1SessionTermination,
|
||||
_KeyLowerName[163:190]: KeyOIDCSingleV1SessionTermination,
|
||||
}
|
||||
|
||||
var _KeyNames = []string{
|
||||
@@ -72,6 +75,7 @@ var _KeyNames = []string{
|
||||
_KeyName[113:133],
|
||||
_KeyName[133:140],
|
||||
_KeyName[140:163],
|
||||
_KeyName[163:190],
|
||||
}
|
||||
|
||||
// KeyString retrieves an enum value from the enum constants string name.
|
||||
|
61
internal/notification/static/i18n/id.yaml
Normal file
61
internal/notification/static/i18n/id.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
InitCode:
|
||||
Title: Inisialisasi Pengguna
|
||||
PreHeader: Inisialisasi Pengguna
|
||||
Subject: Inisialisasi Pengguna
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Pengguna ini telah dibuat. {{.PreferredLoginName}} untuk masuk. {{.Code}}) Jika Anda tidak meminta email ini, abaikan saja.
|
||||
ButtonText: Selesaikan inisialisasi
|
||||
PasswordReset:
|
||||
Title: Setel ulang kata sandi
|
||||
PreHeader: Setel ulang kata sandi
|
||||
Subject: Setel ulang kata sandi
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Kami menerima permintaan pengaturan ulang kata sandi. {{.Code}}) Jika Anda tidak meminta email ini, abaikan saja.
|
||||
ButtonText: Setel ulang kata sandi
|
||||
VerifyEmail:
|
||||
Title: Verifikasi email
|
||||
PreHeader: Verifikasi email
|
||||
Subject: Verifikasi email
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Email baru telah ditambahkan. {{.Code}}) Jika Anda tidak menambahkan email baru, harap abaikan email ini.
|
||||
ButtonText: Verifikasi email
|
||||
VerifyPhone:
|
||||
Title: Verifikasi telepon
|
||||
PreHeader: Verifikasi telepon
|
||||
Subject: Verifikasi telepon
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: 'Nomor telepon baru telah ditambahkan. {{.Code}}'
|
||||
ButtonText: Verifikasi telepon
|
||||
VerifyEmailOTP:
|
||||
Title: Verifikasi Kata Sandi Sekali Pakai
|
||||
PreHeader: Verifikasi Kata Sandi Sekali Pakai
|
||||
Subject: Verifikasi Kata Sandi Sekali Pakai
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Silakan gunakan kata sandi satu kali {{.OTP}} untuk mengautentikasi dalam lima menit berikutnya atau klik tombol "Otentikasi".
|
||||
ButtonText: Otentikasi
|
||||
VerifySMSOTP:
|
||||
Text: >-
|
||||
{{.OTP}} adalah kata sandi satu kali Anda untuk {{ .Domain }}. {{.Expiry}}.
|
||||
|
||||
{{.Domain}} #{{.OTP}}
|
||||
DomainClaimed:
|
||||
Title: Domain telah diklaim
|
||||
PreHeader: Ganti email/nama pengguna
|
||||
Subject: Domain telah diklaim
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Domainnya {{.Domain}} telah diklaim oleh suatu organisasi. {{.Username}} bukan bagian dari organisasi ini. {{.TempUsername}}) untuk login ini.
|
||||
ButtonText: Login
|
||||
PasswordlessRegistration:
|
||||
Title: Tambahkan Login Tanpa Kata Sandi
|
||||
PreHeader: Tambahkan Login Tanpa Kata Sandi
|
||||
Subject: Tambahkan Login Tanpa Kata Sandi
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Kami menerima permintaan untuk menambahkan token untuk login tanpa kata sandi.
|
||||
ButtonText: Tambahkan Login Tanpa Kata Sandi
|
||||
PasswordChange:
|
||||
Title: Kata sandi pengguna telah berubah
|
||||
PreHeader: Ubah kata sandi
|
||||
Subject: Kata sandi pengguna telah berubah
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: 'Kata sandi pengguna Anda telah berubah. '
|
||||
ButtonText: Login
|
@@ -18,6 +18,7 @@ type InstanceFeatures struct {
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
WebKey FeatureSource[bool]
|
||||
DebugOIDCParentError FeatureSource[bool]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
||||
|
@@ -69,6 +69,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceImprovedPerformanceEventType,
|
||||
feature_v2.InstanceWebKeyEventType,
|
||||
feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -92,6 +93,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
|
||||
m.instance.TokenExchange = m.system.TokenExchange
|
||||
m.instance.Actions = m.system.Actions
|
||||
m.instance.ImprovedPerformance = m.system.ImprovedPerformance
|
||||
m.instance.OIDCSingleV1SessionTermination = m.system.OIDCSingleV1SessionTermination
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -121,6 +123,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
|
||||
features.WebKey.set(level, event.Value)
|
||||
case feature.KeyDebugOIDCParentError:
|
||||
features.DebugOIDCParentError.set(level, event.Value)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
features.OIDCSingleV1SessionTermination.set(level, event.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -96,6 +96,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
|
||||
|
@@ -27,6 +27,7 @@ type SystemFeatures struct {
|
||||
TokenExchange FeatureSource[bool]
|
||||
Actions FeatureSource[bool]
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
||||
|
@@ -57,6 +57,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemTokenExchangeEventType,
|
||||
feature_v2.SystemActionsEventType,
|
||||
feature_v2.SystemImprovedPerformanceEventType,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -88,6 +89,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
|
||||
features.Actions.set(level, event.Value)
|
||||
case feature.KeyImprovedPerformance:
|
||||
features.ImprovedPerformance.set(level, event.Value)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
features.OIDCSingleV1SessionTermination.set(level, event.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -72,6 +72,7 @@ type ApprovedEvent struct {
|
||||
AuthTime time.Time
|
||||
PreferredLanguage *language.Tag
|
||||
UserAgent *domain.UserAgent
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func (e *ApprovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||
@@ -95,17 +96,19 @@ func NewApprovedEvent(
|
||||
authTime time.Time,
|
||||
preferredLanguage *language.Tag,
|
||||
userAgent *domain.UserAgent,
|
||||
sessionID string,
|
||||
) *ApprovedEvent {
|
||||
return &ApprovedEvent{
|
||||
eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx, aggregate, ApprovedEventType,
|
||||
),
|
||||
userID,
|
||||
userOrgID,
|
||||
userAuthMethods,
|
||||
authTime,
|
||||
preferredLanguage,
|
||||
userAgent,
|
||||
UserID: userID,
|
||||
UserOrgID: userOrgID,
|
||||
UserAuthMethods: userAuthMethods,
|
||||
AuthTime: authTime,
|
||||
PreferredLanguage: preferredLanguage,
|
||||
UserAgent: userAgent,
|
||||
SessionID: sessionID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemTokenExchangeEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemActionsEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemImprovedPerformanceEventType, eventstore.GenericEventMapper[SetEvent[[]feature.ImprovedPerformanceType]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
@@ -25,4 +26,5 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceImprovedPerformanceEventType, eventstore.GenericEventMapper[SetEvent[[]feature.ImprovedPerformanceType]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceWebKeyEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceDebugOIDCParentErrorEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ var (
|
||||
SystemTokenExchangeEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyTokenExchange)
|
||||
SystemActionsEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyActions)
|
||||
SystemImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyImprovedPerformance)
|
||||
SystemOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyOIDCSingleV1SessionTermination)
|
||||
|
||||
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
|
||||
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
|
||||
@@ -30,6 +31,7 @@ var (
|
||||
InstanceImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyImprovedPerformance)
|
||||
InstanceWebKeyEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyWebKey)
|
||||
InstanceDebugOIDCParentErrorEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyDebugOIDCParentError)
|
||||
InstanceOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyOIDCSingleV1SessionTermination)
|
||||
)
|
||||
|
||||
const (
|
||||
|
1365
internal/static/i18n/id.yaml
Normal file
1365
internal/static/i18n/id.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ type UserSessionView struct {
|
||||
MultiFactorVerification time.Time
|
||||
MultiFactorVerificationType domain.MFAType
|
||||
Sequence uint64
|
||||
ID string
|
||||
}
|
||||
|
||||
type UserSessionSearchRequest struct {
|
||||
|
@@ -0,0 +1,11 @@
|
||||
SELECT
|
||||
s.user_agent_id,
|
||||
s.user_id
|
||||
FROM auth.user_sessions s
|
||||
JOIN auth.user_sessions s2
|
||||
ON s.instance_id = s2.instance_id
|
||||
AND s.user_agent_id = s2.user_agent_id
|
||||
WHERE
|
||||
s2.id = $1
|
||||
AND s.instance_id = $2
|
||||
AND s.state = 0;
|
@@ -32,6 +32,7 @@ const (
|
||||
UserSessionKeyPasswordlessVerification = "passwordless_verification"
|
||||
UserSessionKeyExternalLoginVerification = "external_login_verification"
|
||||
UserSessionKeySelectedIDPConfigID = "selected_idp_config_id"
|
||||
UserSessionKeyID = "id"
|
||||
)
|
||||
|
||||
type UserSessionView struct {
|
||||
@@ -59,6 +60,12 @@ type UserSessionView struct {
|
||||
MultiFactorVerificationType sql.NullInt32 `json:"-" gorm:"column:multi_factor_verification_type"`
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"`
|
||||
ID sql.NullString `json:"id" gorm:"-"`
|
||||
}
|
||||
|
||||
type ActiveUserAgentUserIDs struct {
|
||||
UserAgentID string
|
||||
UserIDs []string
|
||||
}
|
||||
|
||||
type userAgentIDPayload struct {
|
||||
@@ -95,6 +102,7 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
|
||||
MultiFactorVerification: userSession.MultiFactorVerification.Time,
|
||||
MultiFactorVerificationType: domain.MFAType(userSession.MultiFactorVerificationType.Int32),
|
||||
Sequence: userSession.Sequence,
|
||||
ID: userSession.ID.String,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
SELECT
|
||||
s.user_agent_id
|
||||
FROM auth.user_sessions s
|
||||
WHERE
|
||||
s.id = $1
|
||||
AND s.instance_id = $2
|
||||
LIMIT 1;
|
@@ -17,7 +17,8 @@ SELECT s.creation_date,
|
||||
s.multi_factor_verification,
|
||||
s.multi_factor_verification_type,
|
||||
s.sequence,
|
||||
s.instance_id
|
||||
s.instance_id,
|
||||
s.id
|
||||
FROM auth.user_sessions s
|
||||
LEFT JOIN projections.users13 u ON s.user_id = u.id AND s.instance_id = u.instance_id
|
||||
LEFT JOIN projections.users13_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
@@ -16,8 +17,15 @@ var userSessionByIDQuery string
|
||||
//go:embed user_sessions_by_user_agent.sql
|
||||
var userSessionsByUserAgentQuery string
|
||||
|
||||
func UserSessionByIDs(db *database.DB, agentID, userID, instanceID string) (userSession *model.UserSessionView, err error) {
|
||||
err = db.QueryRow(
|
||||
//go:embed user_agent_by_user_session_id.sql
|
||||
var userAgentByUserSessionIDQuery string
|
||||
|
||||
//go:embed active_user_ids_by_session_id.sql
|
||||
var activeUserIDsBySessionIDQuery string
|
||||
|
||||
func UserSessionByIDs(ctx context.Context, db *database.DB, agentID, userID, instanceID string) (userSession *model.UserSessionView, err error) {
|
||||
err = db.QueryRowContext(
|
||||
ctx,
|
||||
func(row *sql.Row) error {
|
||||
userSession, err = scanUserSession(row)
|
||||
return err
|
||||
@@ -29,8 +37,10 @@ func UserSessionByIDs(db *database.DB, agentID, userID, instanceID string) (user
|
||||
)
|
||||
return userSession, err
|
||||
}
|
||||
func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSessions []*model.UserSessionView, err error) {
|
||||
err = db.Query(
|
||||
|
||||
func UserSessionsByAgentID(ctx context.Context, db *database.DB, agentID, instanceID string) (userSessions []*model.UserSessionView, err error) {
|
||||
err = db.QueryContext(
|
||||
ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
userSessions, err = scanUserSessions(rows)
|
||||
return err
|
||||
@@ -42,6 +52,51 @@ func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSes
|
||||
return userSessions, err
|
||||
}
|
||||
|
||||
func UserAgentIDBySessionID(ctx context.Context, db *database.DB, sessionID, instanceID string) (userAgentID string, err error) {
|
||||
err = db.QueryRowContext(
|
||||
ctx,
|
||||
func(row *sql.Row) error {
|
||||
return row.Scan(&userAgentID)
|
||||
},
|
||||
userAgentByUserSessionIDQuery,
|
||||
sessionID,
|
||||
instanceID,
|
||||
)
|
||||
return userAgentID, err
|
||||
}
|
||||
|
||||
// ActiveUserIDsBySessionID returns all userIDs with an active session on the same user agent (its id is also returned) based on a sessionID
|
||||
func ActiveUserIDsBySessionID(ctx context.Context, db *database.DB, sessionID, instanceID string) (userAgentID string, userIDs []string, err error) {
|
||||
err = db.QueryContext(
|
||||
ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
userAgentID, userIDs, err = scanActiveUserAgentUserIDs(rows)
|
||||
return err
|
||||
},
|
||||
activeUserIDsBySessionIDQuery,
|
||||
sessionID,
|
||||
instanceID,
|
||||
)
|
||||
return userAgentID, userIDs, err
|
||||
}
|
||||
|
||||
func scanActiveUserAgentUserIDs(rows *sql.Rows) (userAgentID string, userIDs []string, err error) {
|
||||
for rows.Next() {
|
||||
var userID string
|
||||
err := rows.Scan(
|
||||
&userAgentID,
|
||||
&userID)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return "", nil, zerrors.ThrowInternal(err, "VIEW-Sbrws", "Errors.Query.CloseRows")
|
||||
}
|
||||
return userAgentID, userIDs, nil
|
||||
}
|
||||
|
||||
func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
session := new(model.UserSessionView)
|
||||
err := row.Scan(
|
||||
@@ -65,6 +120,7 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
&session.MultiFactorVerificationType,
|
||||
&session.Sequence,
|
||||
&session.InstanceID,
|
||||
&session.ID,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "VIEW-NGBs1", "Errors.UserSession.NotFound")
|
||||
@@ -97,6 +153,7 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
|
||||
&session.MultiFactorVerificationType,
|
||||
&session.Sequence,
|
||||
&session.InstanceID,
|
||||
&session.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -17,7 +17,8 @@ SELECT s.creation_date,
|
||||
s.multi_factor_verification,
|
||||
s.multi_factor_verification_type,
|
||||
s.sequence,
|
||||
s.instance_id
|
||||
s.instance_id,
|
||||
s.id
|
||||
FROM auth.user_sessions s
|
||||
LEFT JOIN projections.users13 u ON s.user_id = u.id AND s.instance_id = u.instance_id
|
||||
LEFT JOIN projections.users13_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id
|
||||
|
Reference in New Issue
Block a user