1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-06-27 16:36:00 +00:00
miniflux-v2/internal/storage/session.go
jvoisin 9a1d9593b3 refactor(crypto): use rand.Text() instead of a custom implementation
Go 1.24 provides the helpful rand.Text() function, returning a base32-encoded
string containing at least 128 bits of randomness. We should make use of it
everywhere it makes sense to do so, if only to not having to think about much
entropy do we need for each cases, and just trust the go crypto team.

Also, rand.Read() can't fail, so no need to check its return value:
https://pkg.go.dev/crypto/rand#Read This behaviour is consistent with go's
standard library itself.
2025-06-18 20:12:55 -07:00

140 lines
3.2 KiB
Go

// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package storage // import "miniflux.app/v2/internal/storage"
import (
"crypto/rand"
"database/sql"
"fmt"
"miniflux.app/v2/internal/model"
)
// CreateAppSessionWithUserPrefs creates a new application session with the given user preferences.
func (s *Storage) CreateAppSessionWithUserPrefs(userID int64) (*model.Session, error) {
user, err := s.UserByID(userID)
if err != nil {
return nil, err
}
session := model.Session{
ID: rand.Text(),
Data: &model.SessionData{
CSRF: rand.Text(),
Theme: user.Theme,
Language: user.Language,
},
}
return s.createAppSession(&session)
}
// CreateAppSession creates a new application session.
func (s *Storage) CreateAppSession() (*model.Session, error) {
session := model.Session{
ID: rand.Text(),
Data: &model.SessionData{
CSRF: rand.Text(),
},
}
return s.createAppSession(&session)
}
func (s *Storage) createAppSession(session *model.Session) (*model.Session, error) {
query := `INSERT INTO sessions (id, data) VALUES ($1, $2)`
_, err := s.db.Exec(query, session.ID, session.Data)
if err != nil {
return nil, fmt.Errorf(`store: unable to create app session: %v`, err)
}
return session, nil
}
// UpdateAppSessionField updates only one session field.
func (s *Storage) UpdateAppSessionField(sessionID, field string, value any) error {
query := `
UPDATE
sessions
SET
data = jsonb_set(data, '{%s}', to_jsonb($1::text), true)
WHERE
id=$2
`
_, err := s.db.Exec(fmt.Sprintf(query, field), value, sessionID)
if err != nil {
return fmt.Errorf(`store: unable to update session field: %v`, err)
}
return nil
}
func (s *Storage) UpdateAppSessionObjectField(sessionID, field string, value interface{}) error {
query := `
UPDATE
sessions
SET
data = jsonb_set(data, '{%s}', $1, true)
WHERE
id=$2
`
_, err := s.db.Exec(fmt.Sprintf(query, field), value, sessionID)
if err != nil {
return fmt.Errorf(`store: unable to update session field: %v`, err)
}
return nil
}
// AppSession returns the given session.
func (s *Storage) AppSession(id string) (*model.Session, error) {
var session model.Session
query := "SELECT id, data FROM sessions WHERE id=$1"
err := s.db.QueryRow(query, id).Scan(
&session.ID,
&session.Data,
)
switch {
case err == sql.ErrNoRows:
return nil, fmt.Errorf(`store: session not found: %s`, id)
case err != nil:
return nil, fmt.Errorf(`store: unable to fetch session: %v`, err)
default:
return &session, nil
}
}
// FlushAllSessions removes all sessions from the database.
func (s *Storage) FlushAllSessions() (err error) {
_, err = s.db.Exec(`DELETE FROM user_sessions`)
if err != nil {
return err
}
_, err = s.db.Exec(`DELETE FROM sessions`)
if err != nil {
return err
}
return nil
}
// CleanOldSessions removes sessions older than specified days.
func (s *Storage) CleanOldSessions(days int) int64 {
query := `
DELETE FROM
sessions
WHERE
created_at < now() - $1::interval
`
result, err := s.db.Exec(query, fmt.Sprintf("%d days", days))
if err != nil {
return 0
}
n, _ := result.RowsAffected()
return n
}