chore(webui): rewrite all the web session code
All checks were successful
main / main (push) Successful in 3m13s
main / deploy (push) Has been skipped
main / publish (push) Has been skipped

#60
This commit is contained in:
Julien Dessaux 2025-04-29 01:25:11 +02:00
parent 929657fd34
commit 3bb5e735c6
Signed by: adyxax
GPG key ID: F92E51B86E07177E
19 changed files with 173 additions and 123 deletions

View file

@ -151,9 +151,12 @@ func (db *DB) LoadAccountUsernames() (map[string]string, error) {
return accounts, nil
}
func (db *DB) LoadAccountById(id uuid.UUID) (*model.Account, error) {
func (db *DB) LoadAccountById(id *uuid.UUID) (*model.Account, error) {
if id == nil {
return nil, nil
}
account := model.Account{
Id: id,
Id: *id,
}
var (
created int64
@ -239,11 +242,15 @@ func (db *DB) SaveAccount(account *model.Account) error {
func (db *DB) SaveAccountSettings(account *model.Account, settings *model.Settings) error {
data, err := json.Marshal(settings)
if err != nil {
return fmt.Errorf("failed to marshal settings for user %s: %w", account.Username, err)
return fmt.Errorf("failed to marshal settings for user account %s: %w", account.Username, err)
}
_, err = db.Exec(`UPDATE accounts SET settings = ? WHERE id = ?`, data, account.Id)
if err != nil {
return fmt.Errorf("failed to update settings for user %s: %w", account.Username, err)
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
}
_, err = db.Exec(`UPDATE sessions SET settings = ? WHERE account_id = ?`, data, account.Id)
if err != nil {
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
}
return nil
}

View file

@ -31,6 +31,7 @@ type DB struct {
ctx context.Context
dataEncryptionKey scrypto.AES256Key
readDB *sql.DB
sessionsSalt scrypto.AES256Key
versionsHistoryLimit int
writeDB *sql.DB
}
@ -82,17 +83,24 @@ func NewDB(ctx context.Context, url string, getenv func(string) string) (*DB, er
return nil, fmt.Errorf("failed to migrate: %w", err)
}
dataEncryptionKey := getenv("DATA_ENCRYPTION_KEY")
dataEncryptionKey := getenv("TFSTATED_DATA_ENCRYPTION_KEY")
if dataEncryptionKey == "" {
return nil, fmt.Errorf("the DATA_ENCRYPTION_KEY environment variable is not set")
return nil, fmt.Errorf("the TFSTATED_DATA_ENCRYPTION_KEY environment variable is not set")
}
if err := db.dataEncryptionKey.FromBase64(dataEncryptionKey); err != nil {
return nil, fmt.Errorf("failed to decode the DATA_ENCRYPTION_KEY environment variable, expected base64: %w", err)
return nil, fmt.Errorf("failed to decode the TFSTATED_DATA_ENCRYPTION_KEY environment variable, expected 32 bytes base64 encoded: %w", err)
}
versionsHistoryLimit := getenv("VERSIONS_HISTORY_LIMIT")
sessionsSalt := getenv("TFSTATED_SESSIONS_SALT")
if sessionsSalt == "" {
return nil, fmt.Errorf("the TFSTATED_SESSIONS_SALT environment variable is not set")
}
if err := db.sessionsSalt.FromBase64(dataEncryptionKey); err != nil {
return nil, fmt.Errorf("failed to decode the TFSTATED_SESSIONS_SALT environment variable, expected 32 bytes base64 encoded: %w", err)
}
versionsHistoryLimit := getenv("TFSTATED_VERSIONS_HISTORY_LIMIT")
if versionsHistoryLimit != "" {
if db.versionsHistoryLimit, err = strconv.Atoi(versionsHistoryLimit); err != nil {
return nil, fmt.Errorf("failed to parse the VERSIONS_HISTORY_LIMIT environment variable, expected an integer: %w", err)
return nil, fmt.Errorf("failed to parse the TFSTATED_VERSIONS_HISTORY_LIMIT environment variable, expected an integer: %w", err)
}
}

View file

@ -7,19 +7,30 @@ import (
"fmt"
"time"
"git.adyxax.org/adyxax/tfstated/pkg/helpers"
"git.adyxax.org/adyxax/tfstated/pkg/model"
"git.adyxax.org/adyxax/tfstated/pkg/scrypto"
"go.n16f.net/uuid"
)
func (db *DB) CreateSession(account *model.Account) (string, error) {
func (db *DB) CreateSession(account *model.Account, settingsData []byte) (string, error) {
sessionBytes := scrypto.RandomBytes(32)
sessionId := base64.RawURLEncoding.EncodeToString(sessionBytes[:])
sessionHash := helpers.HashSessionId(sessionBytes, db.sessionsSalt.Bytes())
var accountId *uuid.UUID = nil
var settings = []byte("{}")
if account != nil {
accountId = &account.Id
settings = account.Settings
} else if settingsData != nil {
settings = settingsData
}
if _, err := db.Exec(
`INSERT INTO sessions(id, account_id, data)
`INSERT INTO sessions(id, account_id, settings)
VALUES (?, ?, ?);`,
sessionId,
account.Id,
"",
sessionHash,
accountId,
settings,
); err != nil {
return "", fmt.Errorf("failed insert new session in database: %w", err)
}
@ -27,7 +38,8 @@ func (db *DB) CreateSession(account *model.Account) (string, error) {
}
func (db *DB) DeleteExpiredSessions() error {
_, err := db.Exec(`DELETE FROM sessions WHERE created < ?`, time.Now().Unix())
expires := time.Now().Add(-12 * time.Hour)
_, err := db.Exec(`DELETE FROM sessions WHERE created < ?`, expires.Unix())
if err != nil {
return fmt.Errorf("failed to delete expired session: %w", err)
}
@ -43,25 +55,30 @@ func (db *DB) DeleteSession(session *model.Session) error {
}
func (db *DB) LoadSessionById(id string) (*model.Session, error) {
sessionBytes, err := base64.RawURLEncoding.DecodeString(id)
if err != nil {
return nil, fmt.Errorf("failed to decode base64 session id: %w", err)
}
sessionHash := helpers.HashSessionId(sessionBytes, db.sessionsSalt.Bytes())
session := model.Session{
Id: id,
Id: sessionHash,
}
var (
created int64
updated int64
)
err := db.QueryRow(
err = db.QueryRow(
`SELECT account_id,
created,
updated,
data
settings
FROM sessions
WHERE id = ?;`,
id,
sessionHash,
).Scan(&session.AccountId,
&created,
&updated,
&session.Data,
&session.Settings,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@ -74,11 +91,13 @@ func (db *DB) LoadSessionById(id string) (*model.Session, error) {
return &session, nil
}
func (db *DB) TouchSession(sessionId string) error {
now := time.Now().UTC()
_, err := db.Exec(`UPDATE sessions SET updated = ? WHERE id = ?`, now.Unix(), sessionId)
if err != nil {
return fmt.Errorf("failed to touch updated for session %s: %w", sessionId, err)
func (db *DB) MigrateSession(session *model.Session, account *model.Account) (string, error) {
if err := db.DeleteSession(session); err != nil {
return "", fmt.Errorf("failed to delete session: %w", err)
}
return nil
sessionId, err := db.CreateSession(account, session.Settings)
if err != nil {
return "", fmt.Errorf("failed to create session: %w", err)
}
return sessionId, nil
}

View file

@ -16,12 +16,11 @@ CREATE TABLE accounts (
CREATE UNIQUE INDEX accounts_username on accounts(username);
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
id BLOB PRIMARY KEY,
account_id TEXT,
created INTEGER NOT NULL DEFAULT (unixepoch()),
updated INTEGER NOT NULL DEFAULT (unixepoch()),
data TEXT NOT NULL,
FOREIGN KEY(account_id) REFERENCES accounts(id) ON DELETE CASCADE
settings BLOB NOT NULL
) STRICT;
CREATE TABLE states (