Documentation
¶
Overview ¶
Package telemetry holds authsome's tracing and metric plumbing: the tracer every authsome span is started on, the helpers that mark a span failed without ever putting error text on it, the bounded values metric labels take, and the instruments recorded through a go-utils metrics sink.
Nothing here reads a request body, a credential or an identifier. Callers pass enums and counts; the helpers refuse to take anything else.
Index ¶
- Constants
- func AuthMethod(raw string) string
- func Bounded(s string) string
- func CallClass(ctx context.Context, err error) string
- func Detach(ctx context.Context) context.Context
- func Fail(span trace.Span, class string, err error)
- func FailErr(span trace.Span, err error)
- func InstrumentClient(c *http.Client, ep Endpoint, m *Metrics) *http.Client
- func ProviderTransport(base http.RoundTripper, ep Endpoint, m *Metrics) http.RoundTripper
- func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
- func StartRoot(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
- func TypeOf(err error) string
- type Endpoint
- type Metrics
- func (t *Metrics) APIKeyAuth(result string)
- func (t *Metrics) AuditRecordFailed()
- func (t *Metrics) ControlDegraded(control string)
- func (t *Metrics) Event(action, resource, outcome string)
- func (t *Metrics) MFAVerification(flow, method, result string)
- func (t *Metrics) OAuth2Token(grantType, result string)
- func (t *Metrics) PasswordHash(operation, algorithm string, took time.Duration)
- func (t *Metrics) ProviderRequest(kind, result string, took time.Duration)
- func (t *Metrics) RateLimiterError(limiter string)
- func (t *Metrics) Refresh(result string)
- func (t *Metrics) RetentionJob(provider, outcome string)
- func (t *Metrics) RetentionRowsDeleted(kind string, n int64)
- func (t *Metrics) RetentionSweep(result string)
- func (t *Metrics) SignIn(method, result string)
- func (t *Metrics) StartSignIn(method string) *SignInAttempt
- func (t *Metrics) Throttled(limiter string)
- type SignInAttempt
Constants ¶
const ( ClassTimeout = "timeout" ClassCanceled = "canceled" ClassDNS = "dns" ClassTLS = "tls" ClassConnection = "connection" ClassOther = "other" )
Transport failure classes. These are the only values CallClass returns.
const ( MetricSignIns = "authsome_signin_attempts_total" MetricMFAVerifications = "authsome_mfa_verifications_total" MetricSessionsRefreshed = "authsome_sessions_refreshed_total" MetricPasswordHashDuration = "authsome_password_hash_duration_seconds" MetricAPIKeyAuths = "authsome_apikey_authentications_total" // #nosec G101 -- a metric name, not a credential MetricOAuth2Tokens = "authsome_oauth2_tokens_requested_total" // #nosec G101 -- a metric name, not a credential MetricProviderDuration = "authsome_provider_request_duration_seconds" MetricRetentionRows = "authsome_retention_rows_deleted_total" MetricRetentionSweeps = "authsome_retention_sweeps_attempted_total" MetricRetentionJobs = "authsome_retention_jobs_processed_total" MetricControlsDegraded = "authsome_controls_degraded_total" MetricRequestsThrottled = "authsome_requests_throttled_total" MetricRateLimiterErrors = "authsome_ratelimiter_errors_total" MetricAuditRecordsFailed = "authsome_audit_records_failed_total" MetricEvents = "authsome_events_total" )
Metric names as authsome records them. Forge's Prometheus export puts its namespace in front, so authsome_signin_attempts_total scrapes as forge_authsome_signin_attempts_total.
const ( AttrResult = attribute.Key("authsome.result") AttrAuthMethod = attribute.Key("authsome.auth.method") AttrMFARequired = attribute.Key("authsome.mfa.required") AttrMFAVerified = attribute.Key("authsome.mfa.verified") AttrSessionFormat = attribute.Key("authsome.session.format") AttrRefreshVia = attribute.Key("authsome.refresh.via") AttrPasswordAlgorithm = attribute.Key("authsome.password.algorithm") AttrPasswordOperation = attribute.Key("authsome.password.operation") AttrPluginName = attribute.Key("authsome.plugin.name") AttrPluginHook = attribute.Key("authsome.plugin.hook") AttrProviderKind = attribute.Key("authsome.provider.kind") AttrMessageChannel = attribute.Key("authsome.message.channel") AttrRetentionRemoved = attribute.Key("authsome.retention.removed") AttrRetentionKind = attribute.Key("authsome.retention.kind") AttrRetentionJobs = attribute.Key("authsome.retention.jobs") AttrRetentionJobKind = attribute.Key("authsome.retention.job_kind") AttrRetentionProvider = attribute.Key("authsome.retention.provider") AttrRetentionOutcome = attribute.Key("authsome.retention.outcome") AttrJWKSSets = attribute.Key("authsome.jwks.sets") AttrJWKSFailed = attribute.Key("authsome.jwks.failed") // The OTel semantic convention keys the client spans use. AttrErrorType = attribute.Key("error.type") AttrExceptionType = attribute.Key("exception.type") AttrHTTPMethod = attribute.Key("http.request.method") AttrHTTPStatusCode = attribute.Key("http.response.status_code") AttrServerAddress = attribute.Key("server.address") AttrServerPort = attribute.Key("server.port") )
Span attribute keys. Every value is an enum, a count or a boolean. None of them carries an ID, an address, a credential or text a caller supplied.
const ( MethodPassword = "password" MethodPasskey = "passkey" MethodSocial = "social" MethodSSO = "sso" MethodMagicLink = "magic_link" MethodPhone = "phone" MethodEmailVerification = "email_verification" MethodOther = "other" )
Values of the method label on authsome_signin_attempts_total and of authsome.auth.method on the issue session span. AuthMethod folds every raw method string into one of these.
const ( SignInOK = "ok" SignInInvalid = "invalid" SignInLocked = "locked" SignInBanned = "banned" SignInUnverified = "unverified" SignInPasswordExpired = "password_expired" SignInMFARequired = "mfa_required" SignInRateLimited = "rate_limited" SignInDenied = "denied" SignInError = "error" )
Values of the result label on authsome_signin_attempts_total.
SignInInvalid covers a wrong password and an identifier nobody holds alike. Nothing authsome records tells those two apart.
const ( MFAFlowSignIn = "signin" MFAFlowVerify = "verify" MFAMethodTOTP = "totp" MFAMethodSMS = "sms" MFAMethodRecovery = "recovery" MFAOK = "ok" MFAInvalid = "invalid" MFAReplayed = "replayed" MFAExpired = "expired" MFAExhausted = "exhausted" MFAError = "error" )
Values of the flow, method and result labels on authsome_mfa_verifications_total.
const ( RefreshOK = "ok" RefreshInvalid = "invalid" RefreshExpired = "expired" RefreshBanned = "banned" RefreshReplayDetected = "replay_detected" RefreshDPoPRejected = "dpop_rejected" RefreshError = "error" )
Values of the result label on authsome_sessions_refreshed_total and of authsome.result on the refresh session span.
const ( RefreshViaRefreshToken = "refresh_token" RefreshViaClient = "oauth2_client" RefreshViaSessionToken = "session_token" RefreshViaSessionID = "session_id" )
Values of authsome.refresh.via on the refresh session span: which credential the caller presented.
const ( PasswordHash = "hash" PasswordVerify = "verify" AlgorithmArgon2id = "argon2id" AlgorithmBcrypt = "bcrypt" )
Values of the operation and algorithm labels on authsome_password_hash_duration_seconds.
const ( APIKeyOK = "ok" APIKeyInvalid = "invalid" APIKeyExpired = "expired" APIKeyThrottled = "throttled" APIKeyDenied = "denied" APIKeyError = "error" )
Values of the result label on authsome_apikey_authentications_total and of authsome.result on the authenticate api key span.
APIKeyInvalid covers a malformed key, an unknown prefix and a wrong secret alike, so the counter cannot be used to learn which prefixes exist.
const ( GrantAuthorizationCode = "authorization_code" GrantRefreshToken = "refresh_token" GrantClientCredentials = "client_credentials" GrantDeviceCode = "device_code" GrantTokenExchange = "token_exchange" GrantOther = "other" // OAuth2Rejected is any other refusal: a bad, used or expired code, a // refresh token that failed, a malformed request, a bad DPoP proof. // OAuth2Pending is a device-code poll answered authorization_pending // or slow_down, which is the flow working as designed. OAuth2OK = "ok" OAuth2InvalidClient = "invalid_client" OAuth2Rejected = "rejected" OAuth2Pending = "pending" OAuth2Error = "error" )
Values of the grant_type and result labels on authsome_oauth2_tokens_requested_total.
const ( ProviderSocial = "social" ProviderOIDC = "oidc" ProviderSAMLMetadata = "saml_metadata" ProviderJWKS = "jwks" ProviderSMS = "sms" ProviderEmail = "email" ProviderCaptcha = "captcha" ProviderBreach = "breach" ProviderCRM = "crm" ProviderWebhook = "webhook" )
Values of the kind label on authsome_provider_request_duration_seconds and of authsome.provider.kind on the client spans.
const ( ProviderOK = "ok" Provider4xx = "4xx" Provider5xx = "5xx" ProviderTimeout = "timeout" ProviderCanceled = "canceled" ProviderError = "error" )
Values of the result label on authsome_provider_request_duration_seconds.
const ( RetentionSessions = "sessions" RetentionVerifications = "verifications" RetentionPasswordResets = "password_resets" RetentionRevokedRefreshTokens = "revoked_refresh_tokens" RetentionCeremonyState = "ceremony_state" RetentionPlugins = "plugins" )
Values of the kind label on authsome_retention_rows_deleted_total and of authsome.retention.kind on the sweep failed event.
const ( SweepOK = "ok" SweepError = "error" SweepSkipped = "skipped" )
Values of the result label on authsome_retention_sweeps_attempted_total.
const ( CRMHubSpot = "hubspot" CRMGeneric = "generic" CRMOther = "other" JobDone = "done" JobRetry = "retry" JobDead = "dead" JobDeferred = "deferred" JobSuppressed = "suppressed" )
Values of the provider and outcome labels on authsome_retention_jobs_processed_total.
const ( ControlRateLimit = "ratelimit" ControlIdentifierLimit = "identifier_limit" ControlSettings = "settings" ControlPasswordHistory = "password_history" ControlBreachCheck = "breach_check" )
Values of the control label on authsome_controls_degraded_total: a security control that could not run and let the request through.
const ( LimiterRoute = "route" LimiterIdentifier = "identifier" )
Values of the limiter label on authsome_requests_throttled_total and authsome_ratelimiter_errors_total.
const ScopeName = "github.com/xraph/authsome"
ScopeName is the instrumentation scope on every span authsome starts.
Variables ¶
This section is empty.
Functions ¶
func AuthMethod ¶
AuthMethod folds the raw auth method an IssueSessionRequest carries into the bounded set above. "social:github" and "sso:acme-okta" lose the part after the colon, which names a provider or an SSO connection, and a "+mfa" suffix is dropped because the second factor has its own counter. Anything unrecognised, including a custom plugin's own method name, is "other".
func Bounded ¶
Bounded folds a hook action, resource or outcome into a label value. The built-in values are dotted lower-case constants; anything that is not (too long, or carrying characters a constant never would) is "other", so a plugin that builds one from input cannot grow the series without bound.
func CallClass ¶
CallClass sorts a transport failure under ctx into timeout, canceled, dns, tls, connection or other. A deadline that has passed or a canceled ctx decides the class whatever err says, since http.Client's own timeout can surface as an error that is neither a context error nor a net.Error.
func Detach ¶
Detach returns ctx without the span it carries, keeping its values and its cancellation.
A goroutine that outlives the call that started it must not keep that call's span: forge runs every extension's Start inside a "start <ext>" span, and grove opens a db.client span under any valid span context, even an ended one. Without this every query an idle loop makes would land in a boot trace that finished at startup.
func Fail ¶
Fail marks span failed with class as its error.type and status description, and adds an exception event carrying only the Go type of err's innermost error. err's text never reaches the span: store, driver and provider errors quote the values they were given.
func InstrumentClient ¶
InstrumentClient returns a copy of c whose transport is c's own wrapped in ProviderTransport. A nil c stands for http.DefaultClient. The copy keeps c's timeout, redirect policy and cookie jar.
func ProviderTransport ¶
func ProviderTransport(base http.RoundTripper, ep Endpoint, m *Metrics) http.RoundTripper
ProviderTransport wraps base (http.DefaultTransport when nil) so every request it sends is measured, and traced under the span in the request's context.
The client span is named after the HTTP method alone and carries http.request.method, authsome.provider.kind, server.address and server.port (unless ep.HideHost), and http.response.status_code. It never carries the URL, its path or its query, or any header. A 4xx or 5xx answer, or a transport failure, marks it failed with error.type set to the status code or to timeout, canceled, dns, tls, connection or other. The error's text never reaches it. With no span in the request's context it records the duration and starts no span, so a call made outside any request does not mint a trace of its own.
The request goes out with exactly the headers its caller set: no traceparent, tracestate or baggage. Providers are third parties, and an inbound request's baggage is not theirs to see.
m may be nil, in which case only the span is recorded.
func Start ¶
func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
Start starts a span on the global tracer provider, which forge.New installs. The tracer is looked up on every call, so a provider installed after the engine was built still gets the spans.
func StartRoot ¶
func StartRoot(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
StartRoot starts the root span of one unit of background work. Pass it a ctx that went through Detach, so nothing the unit does before or after the span lands in a trace that ended long ago.
Types ¶
type Endpoint ¶
type Endpoint struct {
// Kind is the provider kind, one of the Provider* constants. It is
// recorded as authsome.provider.kind and as the kind label.
Kind string
// HideHost keeps server.address and server.port off the span. Set it
// when a customer supplies the URL (an SSO issuer, a SAML metadata URL,
// a webhook or a generic CRM endpoint): the host is then theirs, not a
// vendor's.
HideHost bool
}
Endpoint describes a third-party call for ProviderTransport. Nothing in it comes from the request URL, which is why a span can't leak the password hash prefix in a breach-check path, an account SID in an SMS path, or a token in a query string.
type Metrics ¶
type Metrics struct {
// contains filtered or unexported fields
}
Metrics holds authsome's instruments. They are declared once, when the engine is built. A nil *Metrics records nothing, which is what an engine built without WithMetricsSink gets; every method is safe to call on it.
func From ¶
From returns the instruments of an engine handed over as a plugin.Engine, or nil when the engine records none (or is a test stub without the method). It is how authsome's own plugins reach the sink without the plugin.Engine interface growing a method every stub would need.
func NewMetrics ¶
NewMetrics declares authsome's instruments in m. A nil m returns nil.
func (*Metrics) APIKeyAuth ¶
APIKeyAuth counts one API key authentication.
func (*Metrics) AuditRecordFailed ¶
func (t *Metrics) AuditRecordFailed()
AuditRecordFailed counts an audit event the trail refused.
func (*Metrics) ControlDegraded ¶
ControlDegraded counts a security control that could not run and let the request through.
func (*Metrics) MFAVerification ¶
MFAVerification counts one second-factor code check.
func (*Metrics) OAuth2Token ¶
OAuth2Token counts one request at the OAuth2 token endpoint.
func (*Metrics) PasswordHash ¶
PasswordHash records how long one password hash or verify took.
func (*Metrics) ProviderRequest ¶
ProviderRequest records how long one third-party call took.
func (*Metrics) RateLimiterError ¶
RateLimiterError counts a rate limiter that could not answer.
func (*Metrics) RetentionJob ¶
RetentionJob counts one CRM outbox job reaching an outcome.
func (*Metrics) RetentionRowsDeleted ¶
RetentionRowsDeleted adds n to the rows the retention sweep removed.
func (*Metrics) RetentionSweep ¶
RetentionSweep counts one retention sweep.
func (*Metrics) StartSignIn ¶
func (t *Metrics) StartSignIn(method string) *SignInAttempt
StartSignIn begins counting an attempt under method, one of the Method* constants.
type SignInAttempt ¶
type SignInAttempt struct {
// contains filtered or unexported fields
}
SignInAttempt counts one sign-in through a plugin's verify or callback handler exactly once. Start it at the top of the handler and defer Done with the handler's error. Call HandOff right before IssueSession, which counts the attempt itself from then on; call Refuse where the handler answers a refusal without returning an error (a redirect carrying the error, say), so Done doesn't read the nil error as success.
A nil *SignInAttempt (from a nil *Metrics) does nothing.
func (*SignInAttempt) Done ¶
func (a *SignInAttempt) Done(err error)
Done records the attempt unless it was handed off. Without a Refuse it reads the result off err: nil is ok, and an HTTP error's status sorts it into invalid (400, 401, 404), denied (403), rate_limited (429) or error.
func (*SignInAttempt) HandOff ¶
func (a *SignInAttempt) HandOff()
HandOff leaves the count to IssueSession.
func (*SignInAttempt) Refuse ¶
func (a *SignInAttempt) Refuse(result string)
Refuse sets the result Done records, whatever error it is given.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package telemetrytest holds the helpers authsome's telemetry tests share: a real Forge metrics registry with its Prometheus scrape, and a span recorder installed as the global tracer provider.
|
Package telemetrytest holds the helpers authsome's telemetry tests share: a real Forge metrics registry with its Prometheus scrape, and a span recorder installed as the global tracer provider. |