diff --git a/server/session.go b/server/session.go index 3737a39..9d78ce6 100644 --- a/server/session.go +++ b/server/session.go @@ -1317,7 +1317,22 @@ func (s *Session) onLogin(msgID string, timestamp time.Time, rec *auth.Rec, miss // GenSecret fails only if tokenLifetime is < 0. It can't be < 0 here, // otherwise login would have failed earlier. rec.Features = features - params["token"], params["expires"], _ = store.Store.GetLogicalAuthHandler("token").GenSecret(rec) + token, expires, _ := store.Store.GetLogicalAuthHandler("token").GenSecret(rec) + params["token"], params["expires"] = token, expires + if !s.uid.IsZero() && features&auth.FeatureNoLogin == 0 { + if enabled, err := store.StoreLoginSession(token, store.LoginSession{ + UserID: store.DecodeUid(rec.Uid), + TenantID: int64(s.tenantID), + TenantCode: s.tenantCode, + DeviceID: s.deviceID, + AuthLevel: rec.AuthLevel.String(), + State: "active", + ExpiresAt: expires.Format(time.RFC3339), + IssuedAt: time.Now().UTC().Format(time.RFC3339), + }, time.Until(expires)); enabled && err != nil { + logs.Warn.Println("s.onLogin: failed to store login session", rec.Uid, err, s.sid) + } + } reply.Ctrl.Params = params return reply diff --git a/server/store/group_message_runtime.go b/server/store/group_message_runtime.go index 33b2d72..e7bd539 100644 --- a/server/store/group_message_runtime.go +++ b/server/store/group_message_runtime.go @@ -62,6 +62,17 @@ type RealtimeFanoutEnvelope struct { Message json.RawMessage `json:"message"` } +type LoginSession struct { + UserID int64 `json:"user_id"` + TenantID int64 `json:"tenant_id"` + TenantCode string `json:"tenant_code"` + DeviceID string `json:"device_id,omitempty"` + AuthLevel string `json:"auth_level"` + State string `json:"state"` + ExpiresAt string `json:"expires_at"` + IssuedAt string `json:"issued_at"` +} + type groupMessageRuntime interface { Close() error Claim(tenantID types.TenantID, topic string, from types.Uid, clientMsgID string) (*groupMessageUniqueRecord, bool, error) @@ -95,6 +106,7 @@ type groupMessageRuntime interface { ClaimShortState(namespace, id string, ttl time.Duration) (bool, error) ClaimShortStateValue(namespace, id, value string, ttl time.Duration) (bool, bool, error) ShortStateExists(namespace, id string) (bool, error) + StoreLoginSession(token []byte, session LoginSession, ttl time.Duration) error } var groupMsgRuntime groupMessageRuntime @@ -190,6 +202,13 @@ func IsLoginTokenRevoked(tenantID types.TenantID, token []byte) (bool, bool, err return revoked, true, err } +func StoreLoginSession(token []byte, session LoginSession, ttl time.Duration) (bool, error) { + if groupMsgRuntime == nil || len(token) == 0 || ttl <= 0 { + return false, nil + } + return true, groupMsgRuntime.StoreLoginSession(token, session, ttl) +} + func RefreshUserPresence(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error { if groupMsgRuntime == nil || !tenantID.IsValid() || uid.IsZero() || sid == "" { return nil @@ -990,6 +1009,18 @@ func (r *redisGroupMessageRuntime) ShortStateExists(namespace, id string) (bool, return count > 0, err } +func (r *redisGroupMessageRuntime) StoreLoginSession(token []byte, session LoginSession, ttl time.Duration) error { + if len(token) == 0 || ttl <= 0 { + return nil + } + digest := sha256.Sum256(token) + payload, err := json.Marshal(session) + if err != nil { + return err + } + return r.client.Set(context.Background(), r.loginSessionKey(hex.EncodeToString(digest[:])), payload, ttl).Err() +} + func (r *redisGroupMessageRuntime) hotSeqIDs(tenantID types.TenantID, topic string, opts *types.QueryOpt, limit int) ([]int64, bool, error) { if opts != nil && len(opts.IdRanges) > 0 { seqs := expandRanges(opts.IdRanges, limit) @@ -1133,6 +1164,10 @@ func (r *redisGroupMessageRuntime) shortStateKey(namespace, id string) string { return r.key(types.ZeroTenantID, "short", namespace, id) } +func (r *redisGroupMessageRuntime) loginSessionKey(tokenHash string) string { + return strings.TrimSuffix(r.prefix, ":") + ":session:token:" + tokenHash +} + func (r *redisGroupMessageRuntime) cacheTTL(namespace string) time.Duration { ttl := r.cfg.CacheTTL if namespace == "verification" || namespace == "action_token" { diff --git a/server/store/group_message_runtime_test.go b/server/store/group_message_runtime_test.go index 0fdd6bf..d7bac37 100644 --- a/server/store/group_message_runtime_test.go +++ b/server/store/group_message_runtime_test.go @@ -171,6 +171,10 @@ func (r *testGroupMessageRuntime) ShortStateExists(namespace, id string) (bool, return r.shortStates[namespace+":"+id], nil } +func (r *testGroupMessageRuntime) StoreLoginSession([]byte, LoginSession, time.Duration) error { + return nil +} + func TestLoginTokenRevocation(t *testing.T) { oldRuntime := groupMsgRuntime groupMsgRuntime = newTestGroupMessageRuntime()