snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:19:12 +05:00
commit c60cc7ca75
8092 changed files with 394160 additions and 0 deletions

596
x/events/events.go Normal file
View File

@@ -0,0 +1,596 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package events
import (
"context"
"errors"
"net/url"
"time"
"github.com/gofrs/uuid"
otelattr "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/ory/herodot"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/x"
"github.com/ory/x/jsonx"
"github.com/ory/x/otelx/semconv"
)
const (
IdentityCreated semconv.Event = "IdentityCreated"
IdentityDeleted semconv.Event = "IdentityDeleted"
IdentityUpdated semconv.Event = "IdentityUpdated"
JsonnetMappingFailed semconv.Event = "JsonnetMappingFailed"
LoginFailed semconv.Event = "LoginFailed"
LoginInitiated semconv.Event = "LoginInitiated"
LoginSucceeded semconv.Event = "LoginSucceeded"
RecoveryFailed semconv.Event = "RecoveryFailed"
RecoveryInitiatedByAdmin semconv.Event = "RecoveryInitiatedByAdmin"
RecoverySucceeded semconv.Event = "RecoverySucceeded"
RegistrationFailed semconv.Event = "RegistrationFailed"
RegistrationInitiated semconv.Event = "RegistrationInitiated"
RegistrationSucceeded semconv.Event = "RegistrationSucceeded"
SessionChanged semconv.Event = "SessionChanged"
SessionChecked semconv.Event = "SessionChecked"
SessionIssued semconv.Event = "SessionIssued"
SessionLifespanExtended semconv.Event = "SessionLifespanExtended"
SessionRevoked semconv.Event = "SessionRevoked"
SessionTokenizedAsJWT semconv.Event = "SessionTokenizedAsJWT"
SettingsFailed semconv.Event = "SettingsFailed"
SettingsSucceeded semconv.Event = "SettingsSucceeded"
VerificationFailed semconv.Event = "VerificationFailed"
VerificationSucceeded semconv.Event = "VerificationSucceeded"
WebhookDelivered semconv.Event = "WebhookDelivered"
WebhookFailed semconv.Event = "WebhookFailed"
WebhookSucceeded semconv.Event = "WebhookSucceeded"
CourierMessageAbandoned semconv.Event = "CourierMessageAbandoned"
CourierMessageDispatched semconv.Event = "CourierMessageDispatched"
)
const (
AttributeKeyErrorReason semconv.AttributeKey = "ErrorReason"
AttributeKeyFlowID semconv.AttributeKey = "FlowID"
AttributeKeyFlowRefresh semconv.AttributeKey = "FlowRefresh"
AttributeKeyFlowRequestedAAL semconv.AttributeKey = "FlowRequestedAAL"
AttributeKeyJsonnetInput semconv.AttributeKey = "JsonnetInput"
AttributeKeyJsonnetOutput semconv.AttributeKey = "JsonnetOutput"
AttributeKeyLoginRequestedAAL semconv.AttributeKey = "LoginRequestedAAL"
AttributeKeyLoginRequestedPrivilegedSession semconv.AttributeKey = "LoginRequestedPrivilegedSession"
AttributeKeyOrganizationID semconv.AttributeKey = "OrganizationID"
AttributeKeyReason semconv.AttributeKey = "Reason" // Deprecated, use AttributeKeyErrorReason
// AttributeKeySelfServiceFlowType is the type of self-service flow, e.g. "api" or "browser".
AttributeKeySelfServiceFlowType semconv.AttributeKey = "SelfServiceFlowType"
// AttributeKeySelfServiceFlowName is the client used in the self-service flow, e.g. "login" or "registration"
AttributeKeySelfServiceFlowName semconv.AttributeKey = "SelfServiceFlowName"
// AttributeKeySelfServiceMethodUsed is the strategy used in the self-service flow, e.g. "oidc" or "password".
AttributeKeySelfServiceMethodUsed semconv.AttributeKey = "SelfServiceMethodUsed"
AttributeKeySelfServiceSSOProviderUsed semconv.AttributeKey = "SelfServiceSSOProviderUsed"
// AttributeKeySelfServiceStrategyUsed is the name of the Flow used in the self-service flow, e.g. "login" or "registration".
//
// Deprecated: use AttributeKeySelfServiceFlowName instead.
AttributeKeySelfServiceStrategyUsed semconv.AttributeKey = "SelfServiceStrategyUsed"
AttributeKeySessionAAL semconv.AttributeKey = "SessionAAL"
AttributeKeySessionExpiresAt semconv.AttributeKey = "SessionExpiresAt"
AttributeKeySessionID semconv.AttributeKey = "SessionID"
AttributeKeyTokenizedSessionTTL semconv.AttributeKey = "TokenizedSessionTTL"
AttributeKeyWebhookAttemptNumber semconv.AttributeKey = "WebhookAttemptNumber"
AttributeKeyWebhookID semconv.AttributeKey = "WebhookID"
AttributeKeyWebhookRequestBody semconv.AttributeKey = "WebhookRequestBody"
AttributeKeyWebhookRequestID semconv.AttributeKey = "WebhookRequestID"
AttributeKeyWebhookResponseBody semconv.AttributeKey = "WebhookResponseBody"
AttributeKeyWebhookResponseStatusCode semconv.AttributeKey = "WebhookResponseStatusCode"
AttributeKeyWebhookTriggerID semconv.AttributeKey = "WebhookTriggerID"
AttributeKeyWebhookURL semconv.AttributeKey = "WebhookURL"
AttributeKeyCourierMessageID semconv.AttributeKey = "CourierMessageID"
AttributeKeyCourierMessageChannel semconv.AttributeKey = "CourierMessageChannel"
AttributeKeyCourierMessageTemplateType semconv.AttributeKey = "CourierMessageTemplateType"
)
func attrSessionID(val uuid.UUID) otelattr.KeyValue {
return otelattr.String(AttributeKeySessionID.String(), val.String())
}
func attrTokenizedSessionTTL(ttl time.Duration) otelattr.KeyValue {
return otelattr.String(AttributeKeyTokenizedSessionTTL.String(), ttl.String())
}
func attrSessionAAL(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeySessionAAL.String(), val)
}
func attLoginRequestedAAL(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeyLoginRequestedAAL.String(), val)
}
func attrFlowRefresh(val bool) otelattr.KeyValue {
return otelattr.Bool(AttributeKeyFlowRefresh.String(), val)
}
func attrOrganizationID(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeyOrganizationID.String(), val)
}
func attrFlowRequestedAAL(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeyFlowRequestedAAL.String(), val)
}
func attSessionExpiresAt(expiresAt time.Time) otelattr.KeyValue {
return otelattr.String(AttributeKeySessionExpiresAt.String(), expiresAt.String())
}
func attLoginRequestedPrivilegedSession(val bool) otelattr.KeyValue {
return otelattr.Bool(AttributeKeyLoginRequestedPrivilegedSession.String(), val)
}
func attrSelfServiceFlowType(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeySelfServiceFlowType.String(), val)
}
func attrSelfServiceMethodUsed(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeySelfServiceMethodUsed.String(), val)
}
func attrSelfServiceSSOProviderUsed(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeySelfServiceSSOProviderUsed.String(), val)
}
func attrWebhookID(id string) otelattr.KeyValue {
return otelattr.String(AttributeKeyWebhookID.String(), id)
}
func attrWebhookURL(URL *url.URL) otelattr.KeyValue {
return otelattr.String(AttributeKeyWebhookURL.String(), URL.Redacted())
}
func attrWebhookReq(body []byte) otelattr.KeyValue {
return otelattr.String(AttributeKeyWebhookRequestBody.String(), string(body))
}
func attrWebhookRes(body []byte) otelattr.KeyValue {
return otelattr.String(AttributeKeyWebhookResponseBody.String(), string(body))
}
func attrWebhookStatus(status int) otelattr.KeyValue {
return otelattr.Int(AttributeKeyWebhookResponseStatusCode.String(), status)
}
func attrWebhookAttempt(n int) otelattr.KeyValue {
return otelattr.Int(AttributeKeyWebhookAttemptNumber.String(), n)
}
func attrWebhookRequestID(id uuid.UUID) otelattr.KeyValue {
return otelattr.String(AttributeKeyWebhookRequestID.String(), id.String())
}
func attrWebhookTriggerID(id uuid.UUID) otelattr.KeyValue {
return otelattr.String(AttributeKeyWebhookTriggerID.String(), id.String())
}
// deprecated: use attrErrorReason instead
func attrReason(err error) otelattr.KeyValue {
return otelattr.String(AttributeKeyReason.String(), reasonForError(err))
}
func attrErrorReason(err error) otelattr.KeyValue {
return otelattr.String(AttributeKeyErrorReason.String(), reasonForError(err))
}
func attrJsonnetInput(in []byte) otelattr.KeyValue {
return otelattr.String(AttributeKeyJsonnetInput.String(), string(jsonx.Anonymize(in)))
}
func attrJsonnetOutput(out string) otelattr.KeyValue {
return otelattr.String(AttributeKeyJsonnetOutput.String(), string(jsonx.Anonymize([]byte(out))))
}
func attrFlowID(id uuid.UUID) otelattr.KeyValue {
return otelattr.String(AttributeKeyFlowID.String(), id.String())
}
func attrCourierMessageID(id uuid.UUID) otelattr.KeyValue {
return otelattr.String(AttributeKeyCourierMessageID.String(), id.String())
}
func attrCourierMessageChannel(channel string) otelattr.KeyValue {
return otelattr.String(AttributeKeyCourierMessageChannel.String(), channel)
}
func attrCourierMessageTemplateType(templateType string) otelattr.KeyValue {
return otelattr.String(AttributeKeyCourierMessageTemplateType.String(), templateType)
}
func NewSessionIssued(ctx context.Context, aal string, sessionID, identityID uuid.UUID) (string, trace.EventOption) {
return SessionIssued.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
attrSessionID(sessionID),
attrSessionAAL(aal),
)...,
)
}
func NewSessionChanged(ctx context.Context, aal string, sessionID, identityID uuid.UUID) (string, trace.EventOption) {
return SessionChanged.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
attrSessionID(sessionID),
attrSessionAAL(aal),
)...,
)
}
func NewSessionLifespanExtended(ctx context.Context, sessionID, identityID uuid.UUID, newExpiry time.Time) (string, trace.EventOption) {
return SessionLifespanExtended.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
attrSessionID(sessionID),
attSessionExpiresAt(newExpiry),
)...,
)
}
type LoginSucceededOpts struct {
SessionID, IdentityID, FlowID uuid.UUID
FlowType, RequestedAAL, Method, SSOProvider string
IsRefresh bool
}
func NewLoginSucceeded(ctx context.Context, o *LoginSucceededOpts) (string, trace.EventOption) {
return LoginSucceeded.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(o.IdentityID),
attrSessionID(o.SessionID),
attrSelfServiceFlowType(o.FlowType),
attLoginRequestedAAL(o.RequestedAAL),
attLoginRequestedPrivilegedSession(o.IsRefresh),
attrSelfServiceMethodUsed(o.Method),
attrSelfServiceSSOProviderUsed(o.SSOProvider),
attrFlowID(o.FlowID),
)...,
)
}
func NewRegistrationSucceeded(ctx context.Context, flowID, identityID uuid.UUID, flowType, method, provider string) (string, trace.EventOption) {
return RegistrationSucceeded.String(),
trace.WithAttributes(append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
semconv.AttrIdentityID(identityID),
attrSelfServiceMethodUsed(method),
attrSelfServiceSSOProviderUsed(provider),
attrFlowID(flowID),
)...)
}
func NewRecoverySucceeded(ctx context.Context, flowID, identityID uuid.UUID, flowType, method string) (string, trace.EventOption) {
return RecoverySucceeded.String(),
trace.WithAttributes(append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
semconv.AttrIdentityID(identityID),
attrSelfServiceMethodUsed(method),
attrFlowID(flowID),
)...)
}
func NewRecoveryInitiatedByAdmin(ctx context.Context, flowID, identityID uuid.UUID, flowType, method string) (string, trace.EventOption) {
return RecoveryInitiatedByAdmin.String(),
trace.WithAttributes(append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
semconv.AttrIdentityID(identityID),
attrSelfServiceMethodUsed(method),
attrFlowID(flowID),
)...)
}
func NewSettingsSucceeded(ctx context.Context, flowID, identityID uuid.UUID, flowType, method string) (string, trace.EventOption) {
return SettingsSucceeded.String(),
trace.WithAttributes(append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
semconv.AttrIdentityID(identityID),
attrSelfServiceMethodUsed(method),
attrFlowID(flowID),
)...)
}
func NewVerificationSucceeded(ctx context.Context, flowID, identityID uuid.UUID, flowType, method string) (string, trace.EventOption) {
return VerificationSucceeded.String(),
trace.WithAttributes(append(
semconv.AttributesFromContext(ctx),
attrSelfServiceMethodUsed(method),
attrSelfServiceFlowType(flowType),
semconv.AttrIdentityID(identityID),
attrFlowID(flowID),
)...)
}
func NewRegistrationFailed(ctx context.Context, flowID uuid.UUID, flowType, method string, err error) (string, trace.EventOption) {
return RegistrationFailed.String(),
trace.WithAttributes(append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
attrSelfServiceMethodUsed(method),
attrReason(err),
attrErrorReason(err),
attrFlowID(flowID),
)...)
}
func NewRecoveryFailed(ctx context.Context, flowID uuid.UUID, flowType, method string, err error) (string, trace.EventOption) {
attrs := append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
attrSelfServiceMethodUsed(method),
attrReason(err),
attrErrorReason(err),
attrFlowID(flowID),
)
var identityIDError *x.WithIdentityIDError
if errors.As(err, &identityIDError) {
attrs = append(attrs, semconv.AttrIdentityID(identityIDError.IdentityID()))
}
return RecoveryFailed.String(), trace.WithAttributes(attrs...)
}
func NewSettingsFailed(ctx context.Context, flowID uuid.UUID, flowType, method string, err error) (string, trace.EventOption) {
attrs := append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
attrSelfServiceMethodUsed(method),
attrReason(err),
attrErrorReason(err),
attrFlowID(flowID),
)
var identityIDError *x.WithIdentityIDError
if errors.As(err, &identityIDError) {
attrs = append(attrs, semconv.AttrIdentityID(identityIDError.IdentityID()))
}
return SettingsFailed.String(), trace.WithAttributes(attrs...)
}
func NewVerificationFailed(ctx context.Context, flowID uuid.UUID, flowType, method string, err error) (string, trace.EventOption) {
attrs := append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
attrSelfServiceMethodUsed(method),
attrReason(err),
attrErrorReason(err),
attrFlowID(flowID),
)
var identityIDError *x.WithIdentityIDError
if errors.As(err, &identityIDError) {
attrs = append(attrs, semconv.AttrIdentityID(identityIDError.IdentityID()))
}
return VerificationFailed.String(),
trace.WithAttributes(attrs...)
}
func NewIdentityCreated(ctx context.Context, identityID uuid.UUID) (string, trace.EventOption) {
return IdentityCreated.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
)...,
)
}
func NewIdentityDeleted(ctx context.Context, identityID uuid.UUID) (string, trace.EventOption) {
return IdentityDeleted.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
)...,
)
}
func NewIdentityUpdated(ctx context.Context, identityID uuid.UUID) (string, trace.EventOption) {
return IdentityUpdated.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
)...,
)
}
func NewLoginFailed(ctx context.Context, flowID uuid.UUID, flowType, method, requestedAAL string, isRefresh bool, err error) (string, trace.EventOption) {
attrs := append(
semconv.AttributesFromContext(ctx),
attrSelfServiceFlowType(flowType),
attLoginRequestedAAL(requestedAAL),
attLoginRequestedPrivilegedSession(isRefresh),
attrSelfServiceMethodUsed(method),
attrReason(err),
attrErrorReason(err),
attrFlowID(flowID),
)
var identityIDError *x.WithIdentityIDError
if errors.As(err, &identityIDError) {
attrs = append(attrs, semconv.AttrIdentityID(identityIDError.IdentityID()))
}
return LoginFailed.String(), trace.WithAttributes(attrs...)
}
func NewSessionRevoked(ctx context.Context, sessionID, identityID uuid.UUID) (string, trace.EventOption) {
return SessionRevoked.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
attrSessionID(sessionID),
)...,
)
}
func NewSessionChecked(ctx context.Context, sessionID, identityID uuid.UUID) (string, trace.EventOption) {
return SessionChecked.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
attrSessionID(sessionID),
)...,
)
}
func NewSessionJWTIssued(ctx context.Context, sessionID, identityID uuid.UUID, ttl time.Duration) (string, trace.EventOption) {
return SessionTokenizedAsJWT.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
semconv.AttrIdentityID(identityID),
attrSessionID(sessionID),
attrTokenizedSessionTTL(ttl),
)...,
)
}
func NewWebhookDelivered(ctx context.Context, URL *url.URL, reqBody []byte, status int, resBody []byte, attempt int, requestID, triggerID uuid.UUID, webhookID string) (string, trace.EventOption) {
return WebhookDelivered.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
attrWebhookReq(reqBody),
attrWebhookRes(resBody),
attrWebhookStatus(status),
attrWebhookURL(URL),
attrWebhookAttempt(attempt),
attrWebhookRequestID(requestID),
attrWebhookID(webhookID),
attrWebhookTriggerID(triggerID),
)...,
)
}
func NewWebhookSucceeded(ctx context.Context, triggerID uuid.UUID, webhookID string) (string, trace.EventOption) {
return WebhookSucceeded.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
attrWebhookID(webhookID),
attrWebhookTriggerID(triggerID),
)...)
}
func NewWebhookFailed(ctx context.Context, err error, triggerID uuid.UUID, id string) (string, trace.EventOption) {
return WebhookFailed.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
attrWebhookID(id),
attrWebhookTriggerID(triggerID),
otelattr.String("Error", err.Error()),
attrErrorReason(err),
)...,
)
}
// NewJsonnetMappingFailed is used to log errors that occur during the Jsonnet
// mapping process. The jsonnetInput and jsonnetOutput is anonymized before
// emitting the event.
func NewJsonnetMappingFailed(ctx context.Context, err error, jsonnetInput []byte, jsonnetOutput, provider string, method string) (string, trace.EventOption) {
attrs := append(
semconv.AttributesFromContext(ctx),
attrErrorReason(err),
attrJsonnetInput(jsonnetInput),
attrSelfServiceSSOProviderUsed(provider),
attrSelfServiceMethodUsed(method),
)
if jsonnetOutput != "" {
attrs = append(attrs, attrJsonnetOutput(jsonnetOutput))
}
return JsonnetMappingFailed.String(),
trace.WithAttributes(
attrs...,
)
}
func NewLoginInitiated(ctx context.Context, flowID uuid.UUID, flowType string, refresh bool, organizationID uuid.NullUUID, requestedAAL string) (string, trace.EventOption) {
attrs := append(semconv.AttributesFromContext(ctx),
attrFlowID(flowID),
attrSelfServiceFlowType(flowType),
attrFlowRefresh(refresh),
attrFlowRequestedAAL(requestedAAL),
)
if organizationID.Valid {
attrs = append(attrs,
attrOrganizationID(organizationID.UUID.String()))
}
return LoginInitiated.String(),
trace.WithAttributes(attrs...)
}
func NewRegistrationInitiated(ctx context.Context, flowID uuid.UUID, flowType string, organizationID uuid.NullUUID) (string, trace.EventOption) {
attrs := append(semconv.AttributesFromContext(ctx),
attrFlowID(flowID),
attrSelfServiceFlowType(flowType),
)
if organizationID.Valid {
attrs = append(attrs,
attrOrganizationID(organizationID.UUID.String()))
}
return RegistrationInitiated.String(),
trace.WithAttributes(attrs...)
}
func reasonForError(err error) string {
if ve := new(schema.ValidationError); errors.As(err, &ve) {
return ve.Message
}
if r := *new(herodot.ReasonCarrier); errors.As(err, &r) {
return r.Reason()
}
return err.Error()
}
func NewCourierMessageAbandoned(ctx context.Context, messageID uuid.UUID, channel string, templateType string) (string, trace.EventOption) {
return CourierMessageAbandoned.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
attrCourierMessageID(messageID),
attrCourierMessageChannel(channel),
attrCourierMessageTemplateType(templateType),
)...,
)
}
func NewCourierMessageDispatched(ctx context.Context, messageID uuid.UUID, channel string, templateType string) (string, trace.EventOption) {
return CourierMessageDispatched.String(),
trace.WithAttributes(
append(
semconv.AttributesFromContext(ctx),
attrCourierMessageID(messageID),
attrCourierMessageChannel(channel),
attrCourierMessageTemplateType(templateType),
)...,
)
}

270
x/events/events_test.go Normal file
View File

@@ -0,0 +1,270 @@
// Copyright © 2025 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package events_test
import (
"errors"
"testing"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/events"
"github.com/ory/x/otelx/semconv"
)
func TestNewJsonnetMappingFailed(t *testing.T) {
tests := []struct {
name string
err error
jsonnetInput []byte
jsonnetOutput string
provider string
method identity.CredentialsType
expectedAttrs []attribute.KeyValue
}{
{
name: "With all attributes",
err: errors.New("test error"),
jsonnetInput: []byte(`{"key": "PII value"}`),
jsonnetOutput: `{"key": 123}`,
provider: "test-provider",
method: identity.CredentialsTypeOIDC,
expectedAttrs: []attribute.KeyValue{
attribute.String("SelfServiceSSOProviderUsed", "test-provider"),
attribute.String("SelfServiceMethodUsed", "oidc"),
attribute.String("ErrorReason", "test error"),
attribute.String("JsonnetInput", `{
"key": "string"
}`),
attribute.String("JsonnetOutput", `{
"key": "number"
}`),
},
},
{
name: "Without JsonnetOutput",
err: errors.New("another error"),
jsonnetInput: []byte(`{"key": "PII value"}`),
jsonnetOutput: "",
provider: "another-provider",
method: identity.CredentialsTypeSAML,
expectedAttrs: []attribute.KeyValue{
attribute.String("SelfServiceSSOProviderUsed", "another-provider"),
attribute.String("SelfServiceMethodUsed", "saml"),
attribute.String("ErrorReason", "another error"),
attribute.String("JsonnetInput", `{
"key": "string"
}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := t.Context()
eventName, opts := events.NewJsonnetMappingFailed(ctx, tt.err, tt.jsonnetInput, tt.jsonnetOutput, tt.provider, tt.method.String())
assert.Equal(t, events.JsonnetMappingFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
assert.ElementsMatch(t, tt.expectedAttrs, eventConfig.Attributes())
})
}
}
func TestNewLoginFailed(t *testing.T) {
ctx := t.Context()
flowID := uuid.Must(uuid.NewV4())
identityID := uuid.Must(uuid.NewV4())
baseErr := errors.New("login failed")
t.Run("case=without identity ID", func(t *testing.T) {
eventName, opts := events.NewLoginFailed(ctx, flowID, "browser", "password", "aal1", false, baseErr)
assert.Equal(t, events.LoginFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
// Should not contain IdentityID attribute
for _, attr := range attrs {
assert.NotEqual(t, "IdentityID", string(attr.Key))
}
// Should contain other attributes
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("LoginRequestedAAL", "aal1"))
assert.Contains(t, attrs, attribute.Bool("LoginRequestedPrivilegedSession", false))
assert.Contains(t, attrs, attribute.String("ErrorReason", "login failed"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "password"))
})
t.Run("case=with identity ID", func(t *testing.T) {
wrappedErr := x.WrapWithIdentityIDError(baseErr, identityID)
eventName, opts := events.NewLoginFailed(ctx, flowID, "browser", "password", "aal1", false, wrappedErr)
assert.Equal(t, events.LoginFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
assert.Contains(t, attrs, attribute.String("IdentityID", identityID.String()))
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("ErrorReason", "login failed"))
})
}
func TestNewRecoveryFailed(t *testing.T) {
ctx := t.Context()
flowID := uuid.Must(uuid.NewV4())
identityID := uuid.Must(uuid.NewV4())
baseErr := errors.New("recovery failed")
t.Run("case=without identity ID", func(t *testing.T) {
eventName, opts := events.NewRecoveryFailed(ctx, flowID, "browser", "code", baseErr)
assert.Equal(t, events.RecoveryFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
for _, attr := range attrs {
assert.NotEqual(t, "IdentityID", string(attr.Key))
}
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "code"))
})
t.Run("case=with identity ID", func(t *testing.T) {
wrappedErr := x.WrapWithIdentityIDError(baseErr, identityID)
eventName, opts := events.NewRecoveryFailed(ctx, flowID, "browser", "link", wrappedErr)
assert.Equal(t, events.RecoveryFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
assert.Contains(t, attrs, attribute.String("IdentityID", identityID.String()))
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "link"))
})
}
func TestNewSettingsFailed(t *testing.T) {
ctx := t.Context()
flowID := uuid.Must(uuid.NewV4())
identityID := uuid.Must(uuid.NewV4())
baseErr := errors.New("settings failed")
t.Run("case=without identity ID", func(t *testing.T) {
eventName, opts := events.NewSettingsFailed(ctx, flowID, "browser", "profile", baseErr)
assert.Equal(t, events.SettingsFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
for _, attr := range attrs {
assert.NotEqual(t, "IdentityID", string(attr.Key))
}
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "profile"))
})
t.Run("case=with identity ID", func(t *testing.T) {
wrappedErr := x.WrapWithIdentityIDError(baseErr, identityID)
eventName, opts := events.NewSettingsFailed(ctx, flowID, "browser", "password", wrappedErr)
assert.Equal(t, events.SettingsFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
assert.Contains(t, attrs, attribute.String("IdentityID", identityID.String()))
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "password"))
})
}
func TestNewVerificationFailed(t *testing.T) {
ctx := t.Context()
flowID := uuid.Must(uuid.NewV4())
identityID := uuid.Must(uuid.NewV4())
baseErr := errors.New("verification failed")
t.Run("case=without identity ID", func(t *testing.T) {
eventName, opts := events.NewVerificationFailed(ctx, flowID, "browser", "code", baseErr)
assert.Equal(t, events.VerificationFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
for _, attr := range attrs {
assert.NotEqual(t, "IdentityID", string(attr.Key))
}
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "code"))
})
t.Run("case=with identity ID", func(t *testing.T) {
wrappedErr := x.WrapWithIdentityIDError(baseErr, identityID)
eventName, opts := events.NewVerificationFailed(ctx, flowID, "browser", "link", wrappedErr)
assert.Equal(t, events.VerificationFailed.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
assert.Contains(t, attrs, attribute.String("IdentityID", identityID.String()))
assert.Contains(t, attrs, attribute.String("SelfServiceFlowType", "browser"))
assert.Contains(t, attrs, attribute.String("SelfServiceMethodUsed", "link"))
})
}
func TestNewCourierMessageDispatched(t *testing.T) {
t.Parallel()
nid := uuid.Must(uuid.NewV4())
msgID := uuid.Must(uuid.NewV4())
ctx := semconv.ContextWithAttributes(t.Context(), attribute.String("ProjectID", nid.String()))
eventName, opts := events.NewCourierMessageDispatched(ctx, msgID, "smtp", "stub")
assert.Equal(t, events.CourierMessageDispatched.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
assert.Contains(t, attrs, attribute.String("ProjectID", nid.String()))
assert.Contains(t, attrs, attribute.String("CourierMessageID", msgID.String()))
assert.Contains(t, attrs, attribute.String("CourierMessageChannel", "smtp"))
assert.Contains(t, attrs, attribute.String("CourierMessageTemplateType", "stub"))
}
func TestNewCourierMessageAbandoned(t *testing.T) {
t.Parallel()
nid := uuid.Must(uuid.NewV4())
msgID := uuid.Must(uuid.NewV4())
ctx := semconv.ContextWithAttributes(t.Context(), attribute.String("ProjectID", nid.String()))
eventName, opts := events.NewCourierMessageAbandoned(ctx, msgID, "http", "recovery")
assert.Equal(t, events.CourierMessageAbandoned.String(), eventName)
eventConfig := trace.NewEventConfig(opts)
attrs := eventConfig.Attributes()
assert.Contains(t, attrs, attribute.String("ProjectID", nid.String()))
assert.Contains(t, attrs, attribute.String("CourierMessageID", msgID.String()))
assert.Contains(t, attrs, attribute.String("CourierMessageChannel", "http"))
assert.Contains(t, attrs, attribute.String("CourierMessageTemplateType", "recovery"))
}