chore(webui): rewrite the web session code again while preparing for csrf tokens
#60
This commit is contained in:
parent
3bb5e735c6
commit
895615ad6e
20 changed files with 162 additions and 149 deletions
|
@ -29,7 +29,7 @@ func (db *DB) CreateAccount(username string, isAdmin bool) (*model.Account, erro
|
|||
return nil, fmt.Errorf("failed to generate password reset uuid: %w", err)
|
||||
}
|
||||
_, err := db.Exec(`INSERT INTO accounts(id, username, is_Admin, settings, password_reset)
|
||||
VALUES (?, ?, ?, ?, ?);`,
|
||||
VALUES (?, ?, ?, jsonb(?), ?);`,
|
||||
accountId,
|
||||
username,
|
||||
isAdmin,
|
||||
|
@ -72,7 +72,7 @@ func (db *DB) InitAdminAccount() error {
|
|||
hash := helpers.HashPassword(password.String(), salt)
|
||||
if _, err := tx.ExecContext(db.ctx,
|
||||
`INSERT INTO accounts(id, username, salt, password_hash, is_admin, settings)
|
||||
VALUES (:id, "admin", :salt, :hash, TRUE, :settings)
|
||||
VALUES (:id, "admin", :salt, :hash, TRUE, jsonb(:settings))
|
||||
ON CONFLICT DO UPDATE SET password_hash = :hash
|
||||
WHERE username = "admin";`,
|
||||
sql.Named("id", accountId),
|
||||
|
@ -91,7 +91,8 @@ func (db *DB) InitAdminAccount() error {
|
|||
|
||||
func (db *DB) LoadAccounts() ([]model.Account, error) {
|
||||
rows, err := db.Query(
|
||||
`SELECT id, username, salt, password_hash, is_admin, created, last_login, settings, password_reset FROM accounts;`)
|
||||
`SELECT id, username, salt, password_hash, is_admin, created, last_login,
|
||||
json_extract(settings, '$'), password_reset FROM accounts;`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load accounts from database: %w", err)
|
||||
}
|
||||
|
@ -102,6 +103,7 @@ func (db *DB) LoadAccounts() ([]model.Account, error) {
|
|||
account model.Account
|
||||
created int64
|
||||
lastLogin int64
|
||||
settings []byte
|
||||
)
|
||||
err = rows.Scan(
|
||||
&account.Id,
|
||||
|
@ -111,11 +113,14 @@ func (db *DB) LoadAccounts() ([]model.Account, error) {
|
|||
&account.IsAdmin,
|
||||
&created,
|
||||
&lastLogin,
|
||||
&account.Settings,
|
||||
&settings,
|
||||
&account.PasswordReset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load account from row: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(settings, &account.Settings); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal account settings: %w", err)
|
||||
}
|
||||
account.Created = time.Unix(created, 0)
|
||||
account.LastLogin = time.Unix(lastLogin, 0)
|
||||
accounts = append(accounts, account)
|
||||
|
@ -161,9 +166,11 @@ func (db *DB) LoadAccountById(id *uuid.UUID) (*model.Account, error) {
|
|||
var (
|
||||
created int64
|
||||
lastLogin int64
|
||||
settings []byte
|
||||
)
|
||||
err := db.QueryRow(
|
||||
`SELECT username, salt, password_hash, is_admin, created, last_login, settings, password_reset
|
||||
`SELECT username, salt, password_hash, is_admin, created, last_login,
|
||||
json_extract(settings, '$'), password_reset
|
||||
FROM accounts
|
||||
WHERE id = ?;`,
|
||||
id,
|
||||
|
@ -173,7 +180,7 @@ func (db *DB) LoadAccountById(id *uuid.UUID) (*model.Account, error) {
|
|||
&account.IsAdmin,
|
||||
&created,
|
||||
&lastLogin,
|
||||
&account.Settings,
|
||||
&settings,
|
||||
&account.PasswordReset)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
@ -181,6 +188,9 @@ func (db *DB) LoadAccountById(id *uuid.UUID) (*model.Account, error) {
|
|||
}
|
||||
return nil, fmt.Errorf("failed to load account by id %s: %w", id, err)
|
||||
}
|
||||
if err := json.Unmarshal(settings, &account.Settings); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal account settings: %w", err)
|
||||
}
|
||||
account.Created = time.Unix(created, 0)
|
||||
account.LastLogin = time.Unix(lastLogin, 0)
|
||||
return &account, nil
|
||||
|
@ -193,9 +203,11 @@ func (db *DB) LoadAccountByUsername(username string) (*model.Account, error) {
|
|||
var (
|
||||
created int64
|
||||
lastLogin int64
|
||||
settings []byte
|
||||
)
|
||||
err := db.QueryRow(
|
||||
`SELECT id, salt, password_hash, is_admin, created, last_login, settings, password_reset
|
||||
`SELECT id, salt, password_hash, is_admin, created, last_login,
|
||||
json_extract(settings, '$'), password_reset
|
||||
FROM accounts
|
||||
WHERE username = ?;`,
|
||||
username,
|
||||
|
@ -205,7 +217,7 @@ func (db *DB) LoadAccountByUsername(username string) (*model.Account, error) {
|
|||
&account.IsAdmin,
|
||||
&created,
|
||||
&lastLogin,
|
||||
&account.Settings,
|
||||
&settings,
|
||||
&account.PasswordReset)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
@ -213,6 +225,9 @@ func (db *DB) LoadAccountByUsername(username string) (*model.Account, error) {
|
|||
}
|
||||
return nil, fmt.Errorf("failed to load account by username %s: %w", username, err)
|
||||
}
|
||||
if err := json.Unmarshal(settings, &account.Settings); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal account settings: %w", err)
|
||||
}
|
||||
account.Created = time.Unix(created, 0)
|
||||
account.LastLogin = time.Unix(lastLogin, 0)
|
||||
return &account, nil
|
||||
|
@ -242,13 +257,20 @@ 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 account %s: %w", account.Username, err)
|
||||
return fmt.Errorf("failed to marshal settings for user accont %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 account settings for user account %s: %w", account.Username, err)
|
||||
}
|
||||
_, err = db.Exec(`UPDATE sessions SET settings = ? WHERE account_id = ?`, data, account.Id)
|
||||
_, err = db.Exec(
|
||||
`UPDATE sessions
|
||||
SET data = jsonb_replace(data,
|
||||
'$.settings', jsonb(:data),
|
||||
'$.account.settings', jsonb(:data))
|
||||
WHERE data->'account'->>'id' = :id`,
|
||||
sql.Named("data", data),
|
||||
sql.Named("id", account.Id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update account settings for user account %s: %w", account.Username, err)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package database
|
|||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
@ -10,31 +11,35 @@ import (
|
|||
"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, settingsData []byte) (string, error) {
|
||||
func (db *DB) CreateSession(sessionData *model.SessionData) (string, *model.Session, 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 sessionData == nil {
|
||||
var err error
|
||||
sessionData, err = model.NewSessionData(nil, nil)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to generate new session data: %w", err)
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(sessionData)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to marshal session data: %w", err)
|
||||
}
|
||||
if _, err := db.Exec(
|
||||
`INSERT INTO sessions(id, account_id, settings)
|
||||
VALUES (?, ?, ?);`,
|
||||
`INSERT INTO sessions(id, data)
|
||||
VALUES (?, jsonb(?));`,
|
||||
sessionHash,
|
||||
accountId,
|
||||
settings,
|
||||
data,
|
||||
); err != nil {
|
||||
return "", fmt.Errorf("failed insert new session in database: %w", err)
|
||||
return "", nil, fmt.Errorf("failed insert new session in database: %w", err)
|
||||
}
|
||||
return sessionId, nil
|
||||
return sessionId, &model.Session{
|
||||
Id: sessionHash,
|
||||
Data: sessionData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DB) DeleteExpiredSessions() error {
|
||||
|
@ -66,38 +71,44 @@ func (db *DB) LoadSessionById(id string) (*model.Session, error) {
|
|||
var (
|
||||
created int64
|
||||
updated int64
|
||||
data []byte
|
||||
)
|
||||
err = db.QueryRow(
|
||||
`SELECT account_id,
|
||||
created,
|
||||
`SELECT created,
|
||||
updated,
|
||||
settings
|
||||
json_extract(data, '$')
|
||||
FROM sessions
|
||||
WHERE id = ?;`,
|
||||
sessionHash,
|
||||
).Scan(&session.AccountId,
|
||||
).Scan(
|
||||
&created,
|
||||
&updated,
|
||||
&session.Settings,
|
||||
)
|
||||
&data)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to load session by id %s: %w", id, err)
|
||||
}
|
||||
if err := json.Unmarshal(data, &session.Data); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal session data: %w", err)
|
||||
}
|
||||
session.Created = time.Unix(created, 0)
|
||||
session.Updated = time.Unix(updated, 0)
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func (db *DB) MigrateSession(session *model.Session, account *model.Account) (string, error) {
|
||||
func (db *DB) MigrateSession(session *model.Session, account *model.Account) (string, *model.Session, error) {
|
||||
if err := db.DeleteSession(session); err != nil {
|
||||
return "", fmt.Errorf("failed to delete session: %w", err)
|
||||
return "", nil, fmt.Errorf("failed to delete session: %w", err)
|
||||
}
|
||||
sessionId, err := db.CreateSession(account, session.Settings)
|
||||
sessionData, err := model.NewSessionData(account, session.Data.Settings)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create session: %w", err)
|
||||
return "", nil, fmt.Errorf("failed to generate new session data: %w", err)
|
||||
}
|
||||
return sessionId, nil
|
||||
sessionId, session, err := db.CreateSession(sessionData)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to create session: %w", err)
|
||||
}
|
||||
return sessionId, session, nil
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@ CREATE TABLE accounts (
|
|||
settings BLOB NOT NULL,
|
||||
password_reset TEXT
|
||||
) STRICT;
|
||||
CREATE UNIQUE INDEX accounts_username on accounts(username);
|
||||
CREATE UNIQUE INDEX accounts_username ON accounts(username);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id BLOB PRIMARY KEY,
|
||||
account_id TEXT,
|
||||
created INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
updated INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
settings BLOB NOT NULL
|
||||
data BLOB NOT NULL
|
||||
) STRICT;
|
||||
CREATE INDEX sessions_data_account_id ON sessions(data->'account'->>'id');
|
||||
|
||||
CREATE TABLE states (
|
||||
id TEXT PRIMARY KEY,
|
||||
|
@ -30,7 +30,7 @@ CREATE TABLE states (
|
|||
created INTEGER DEFAULT (unixepoch()),
|
||||
updated INTEGER DEFAULT (unixepoch())
|
||||
) STRICT;
|
||||
CREATE UNIQUE INDEX states_path on states(path);
|
||||
CREATE UNIQUE INDEX states_path ON states(path);
|
||||
|
||||
CREATE TABLE versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue