diff --git a/pkg/coredata/migrations/20251219T120000Z.sql b/pkg/coredata/migrations/20251219T120000Z.sql new file mode 100644 index 000000000..79cd4989f --- /dev/null +++ b/pkg/coredata/migrations/20251219T120000Z.sql @@ -0,0 +1,24 @@ +CREATE TYPE session_auth_method AS ENUM ( + 'PASSWORD', + 'SAML' +); + +ALTER TABLE sessions ADD COLUMN auth_method session_auth_method; +ALTER TABLE sessions ADD COLUMN authenticated_at TIMESTAMP; + +-- Expire all existing sessions as they are not backward compatible +UPDATE sessions SET + expire_reason = 'revoked', + expired_at = NOW(), + updated_at = NOW(), + auth_method = 'PASSWORD', + authenticated_at = created_at +WHERE expire_reason IS NULL; + +UPDATE sessions SET + auth_method = 'PASSWORD', + authenticated_at = created_at +WHERE auth_method IS NULL; + +ALTER TABLE sessions ALTER COLUMN auth_method SET NOT NULL; +ALTER TABLE sessions ALTER COLUMN authenticated_at SET NOT NULL; diff --git a/pkg/coredata/session.go b/pkg/coredata/session.go index d3e98f7fb..ce3a7f55a 100644 --- a/pkg/coredata/session.go +++ b/pkg/coredata/session.go @@ -35,6 +35,8 @@ type ( TenantID *gid.TenantID `db:"tenant_id"` ParentSessionID *gid.GID `db:"parent_session_id"` Data SessionData `db:"data"` + AuthMethod AuthMethod `db:"auth_method"` + AuthenticatedAt time.Time `db:"authenticated_at"` UserAgent string `db:"user_agent"` IPAddress net.IP `db:"ip_address"` ExpireReason *ExpireReason `db:"expire_reason"` @@ -45,25 +47,26 @@ type ( Sessions []*Session - SessionData struct { - PasswordAuthenticated bool `json:"password_authenticated"` - SAMLAuthenticatedOrgs map[string]SAMLAuthInfo `json:"saml_authenticated_orgs,omitempty"` - } + SessionData struct{} - SAMLAuthInfo struct { - AuthenticatedAt time.Time `json:"authenticated_at"` - SAMLConfigID gid.GID `json:"saml_config_id"` - SAMLSubject string `json:"saml_subject"` - } + AuthMethod string ) -func NewRootSession(userID gid.GID, duration time.Duration) *Session { +const ( + AuthMethodPassword AuthMethod = "PASSWORD" + AuthMethodSAML AuthMethod = "SAML" +) + +func NewRootSession(userID gid.GID, method AuthMethod, duration time.Duration) *Session { + now := time.Now() return &Session{ - ID: gid.New(gid.NilTenant, SessionEntityType), - UserID: userID, - ExpiredAt: time.Now().Add(duration), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + ID: gid.New(gid.NilTenant, SessionEntityType), + UserID: userID, + ExpiredAt: now.Add(duration), + AuthMethod: method, + AuthenticatedAt: now, + CreatedAt: now, + UpdatedAt: now, } } @@ -100,6 +103,8 @@ SELECT tenant_id, data, parent_session_id, + auth_method, + authenticated_at, expire_reason, user_agent, ip_address, @@ -139,13 +144,15 @@ func (s *Session) Insert( ) error { q := ` INSERT INTO - sessions (id, user_id, tenant_id, data, parent_session_id, expire_reason, user_agent, ip_address, expired_at, created_at, updated_at) + sessions (id, user_id, tenant_id, data, parent_session_id, auth_method, authenticated_at, expire_reason, user_agent, ip_address, expired_at, created_at, updated_at) VALUES ( @session_id, @user_id, @tenant_id, @data, @parent_session_id, + @auth_method, + @authenticated_at, @expire_reason, @user_agent, @ip_address, @@ -161,6 +168,8 @@ VALUES ( "tenant_id": s.TenantID, "data": s.Data, "parent_session_id": s.ParentSessionID, + "auth_method": s.AuthMethod, + "authenticated_at": s.AuthenticatedAt, "expire_reason": s.ExpireReason, "user_agent": s.UserAgent, "ip_address": s.IPAddress, @@ -220,6 +229,8 @@ SELECT tenant_id, data, parent_session_id, + auth_method, + authenticated_at, expire_reason, user_agent, ip_address, @@ -309,6 +320,8 @@ SELECT tenant_id, data, parent_session_id, + auth_method, + authenticated_at, expire_reason, user_agent, ip_address, diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index 3e9a0c201..c8303e765 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -172,7 +172,7 @@ func (s *AuthService) CreateIdentityFromInvitation( return fmt.Errorf("cannot insert user: %w", err) } - session = coredata.NewRootSession(user.ID, s.sessionDuration) + session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, s.sessionDuration) err = session.Insert(ctx, tx) if err != nil { return fmt.Errorf("cannot insert session: %w", err) @@ -330,17 +330,7 @@ func (s AuthService) CreateIdentityWithPassword( UpdatedAt: now, } - session = &coredata.Session{ - ID: gid.New(gid.NilTenant, coredata.SessionEntityType), - UserID: user.ID, - Data: coredata.SessionData{ - PasswordAuthenticated: true, - SAMLAuthenticatedOrgs: make(map[string]coredata.SAMLAuthInfo), - }, - ExpiredAt: now.Add(24 * time.Hour * 7), // 7 days, TODO must to be hardcoded here - CreatedAt: now, - UpdatedAt: now, - } + session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, 24*time.Hour*7) ) confirmationToken, err := statelesstoken.NewToken( @@ -416,7 +406,7 @@ func (s AuthService) OpenSessionWithSAML(ctx context.Context, userID gid.GID, or err := s.pg.WithTx( ctx, func(conn pg.Conn) (err error) { - session = coredata.NewRootSession(userID, s.sessionDuration) + session = coredata.NewRootSession(userID, coredata.AuthMethodSAML, s.sessionDuration) err = session.Insert(ctx, conn) if err != nil { return fmt.Errorf("cannot insert session: %w", err) @@ -474,7 +464,7 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add return NewInvalidCredentialsError("invalid email or password") } - session = coredata.NewRootSession(user.ID, s.sessionDuration) + session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, s.sessionDuration) err = session.Insert(ctx, conn) if err != nil { return fmt.Errorf("cannot insert session: %w", err)