diff --git a/docs/api/identity.md b/docs/api/identity.md index edc68e08..efaa841f 100644 --- a/docs/api/identity.md +++ b/docs/api/identity.md @@ -67,4 +67,5 @@ This resource provides information about user authentication sessions, including | `ip` | string | The IP address from which the session was created (optional). | | `fingerprintID` | string | A fingerprint identifier for the session (optional). | | `createdAt` | metav1.Time | The timestamp when the session was created. | -| `expiresAt` | *metav1.Time | The timestamp when the session expires (optional). | +| `lastUpdatedAt` | *metav1.Time | Last time the authentication provider updated this session (optional). | +| `userAgent` | string | Client User-Agent string for this session, if the provider supplies it (optional). | diff --git a/internal/apiserver/identity/sessions/rest.go b/internal/apiserver/identity/sessions/rest.go index ce1dc8c4..30482c89 100644 --- a/internal/apiserver/identity/sessions/rest.go +++ b/internal/apiserver/identity/sessions/rest.go @@ -2,6 +2,7 @@ package sessions import ( "context" + "strings" "time" identityv1alpha1 "go.miloapis.com/milo/pkg/apis/identity/v1alpha1" @@ -97,14 +98,22 @@ func (r *REST) Delete(ctx context.Context, name string, _ rest.ValidateObjectFun func (r *REST) Destroy() {} -// Satisfy rest.TableConvertor with a no-op conversion (returning nil uses default table printer) +func truncateForTable(s string, max int) string { + if max <= 0 || len(s) <= max { + return s + } + return strings.TrimSpace(s[:max]) + "…" +} + func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { table := &metav1.Table{ ColumnDefinitions: []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Provider", Type: "string"}, - {Name: "UserUID", Type: "string"}, - {Name: "Age", Type: "date"}, + {Name: "Name", Type: "string", Format: "name", Description: "Metadata name of the session.", Priority: 0}, + {Name: "Provider", Type: "string", Description: "Authentication provider.", Priority: 0}, + {Name: "Age", Type: "date", Description: "Creation timestamp.", Priority: 0}, + {Name: "User agent", Type: "string", Description: "Client User-Agent (truncated in table view).", Priority: 1}, + {Name: "Last updated", Type: "string", Description: "Provider last update time (RFC3339), if known.", Priority: 1}, + {Name: "UserUID", Type: "string", Description: "Owning user UID.", Priority: 1}, }, } @@ -115,8 +124,19 @@ func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO // metav1.Table wants a date in the cell; pass the timestamp age = s.CreationTimestamp } + lastUpdated := "" + if s.Status.LastUpdatedAt != nil { + lastUpdated = s.Status.LastUpdatedAt.Time.Format(time.RFC3339) + } table.Rows = append(table.Rows, metav1.TableRow{ - Cells: []interface{}{s.Name, s.Status.Provider, s.Status.UserUID, age.Time.Format(time.RFC3339)}, + Cells: []interface{}{ + s.Name, + s.Status.Provider, + age.Time.Format(time.RFC3339), + truncateForTable(s.Status.UserAgent, 80), + lastUpdated, + s.Status.UserUID, + }, Object: runtime.RawExtension{Object: s}, }) } diff --git a/pkg/apis/identity/v1alpha1/types.go b/pkg/apis/identity/v1alpha1/types.go index 912d3935..829a1bdb 100644 --- a/pkg/apis/identity/v1alpha1/types.go +++ b/pkg/apis/identity/v1alpha1/types.go @@ -12,13 +12,29 @@ type Session struct { Status SessionStatus `json:"status,omitempty"` } +// SessionStatus contains session metadata exposed for display and management. +// All fields except those required for identity are optional and populated by the authentication provider. type SessionStatus struct { - UserUID string `json:"userUID"` - Provider string `json:"provider"` - IP string `json:"ip,omitempty"` - FingerprintID string `json:"fingerprintID,omitempty"` - CreatedAt metav1.Time `json:"createdAt"` - ExpiresAt *metav1.Time `json:"expiresAt,omitempty"` + // UserUID is the unique identifier of the user who owns this session. + UserUID string `json:"userUID"` + + // Provider is the authentication provider for this session (e.g. "zitadel"). + Provider string `json:"provider"` + + // IP is the client IP address associated with the session, if known. + IP string `json:"ip,omitempty"` + + // FingerprintID is an optional device or client fingerprint from the provider. + FingerprintID string `json:"fingerprintID,omitempty"` + + // CreatedAt is when the session was created. + CreatedAt metav1.Time `json:"createdAt"` + + // LastUpdatedAt is the last time the provider updated this session (e.g. Zitadel change_date). + LastUpdatedAt *metav1.Time `json:"lastUpdatedAt,omitempty"` + + // UserAgent is the client User-Agent string for this session, if the provider supplies it. + UserAgent string `json:"userAgent,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/identity/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/identity/v1alpha1/zz_generated.deepcopy.go index 73588aed..0bf61d2e 100644 --- a/pkg/apis/identity/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/identity/v1alpha1/zz_generated.deepcopy.go @@ -70,8 +70,8 @@ func (in *SessionList) DeepCopyObject() runtime.Object { func (in *SessionStatus) DeepCopyInto(out *SessionStatus) { *out = *in in.CreatedAt.DeepCopyInto(&out.CreatedAt) - if in.ExpiresAt != nil { - in, out := &in.ExpiresAt, &out.ExpiresAt + if in.LastUpdatedAt != nil { + in, out := &in.LastUpdatedAt, &out.LastUpdatedAt *out = (*in).DeepCopy() } } diff --git a/pkg/apis/identity/v1alpha1/zz_generated.openapi.go b/pkg/apis/identity/v1alpha1/zz_generated.openapi.go index 253a22d2..621e351f 100644 --- a/pkg/apis/identity/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/identity/v1alpha1/zz_generated.openapi.go @@ -115,43 +115,57 @@ func schema_pkg_apis_identity_v1alpha1_SessionStatus(ref common.ReferenceCallbac return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "SessionStatus contains session metadata exposed for display and management. All fields except those required for identity are optional and populated by the authentication provider.", + Type: []string{"object"}, Properties: map[string]spec.Schema{ "userUID": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "UserUID is the unique identifier of the user who owns this session.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, "provider": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "Provider is the authentication provider for this session (e.g. \"zitadel\").", + Default: "", + Type: []string{"string"}, + Format: "", }, }, "ip": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "IP is the client IP address associated with the session, if known.", + Type: []string{"string"}, + Format: "", }, }, "fingerprintID": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "FingerprintID is an optional device or client fingerprint from the provider.", + Type: []string{"string"}, + Format: "", }, }, "createdAt": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + Description: "CreatedAt is when the session was created.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), }, }, - "expiresAt": { + "lastUpdatedAt": { SchemaProps: spec.SchemaProps{ - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + Description: "LastUpdatedAt is the last time the provider updated this session (e.g. Zitadel change_date).", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "userAgent": { + SchemaProps: spec.SchemaProps{ + Description: "UserAgent is the client User-Agent string for this session, if the provider supplies it.", + Type: []string{"string"}, + Format: "", }, }, },