Skip to content

Commit

Permalink
sessions: Implement mysql db.
Browse files Browse the repository at this point in the history
This diff adds a mysql implementation for the sessions.DB interface.
  • Loading branch information
amass01 committed Nov 24, 2021
1 parent ee56e9c commit fdc6e23
Show file tree
Hide file tree
Showing 5 changed files with 454 additions and 14 deletions.
26 changes: 13 additions & 13 deletions politeiawww/legacy/user/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,35 +57,35 @@ const tableKeyValue = `

// tableUsers defines the users table.
const tableUsers = `
id VARCHAR(36) NOT NULL PRIMARY KEY,
username VARCHAR(64) NOT NULL,
u_blob LONGBLOB NOT NULL,
id VARCHAR(36) NOT NULL PRIMARY KEY,
username VARCHAR(64) NOT NULL,
u_blob LONGBLOB NOT NULL,
created_at INT(11) NOT NULL,
updated_at INT(11),
UNIQUE (username)
`

// tableIdentities defines the identities table.
const tableIdentities = `
public_key CHAR(64) NOT NULL PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
activated INT(11) NOT NULL,
public_key CHAR(64) NOT NULL PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
activated INT(11) NOT NULL,
deactivated INT(11) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
`

// tableSessions defines the sessions table.
const tableSessions = `
k CHAR(64) NOT NULL PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
k CHAR(64) NOT NULL PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
created_at INT(11) NOT NULL,
s_blob BLOB NOT NULL
s_blob BLOB NOT NULL
`

// tableEmailHistories defines the email_histories table.
const tableEmailHistories = `
user_id VARCHAR(36) NOT NULL PRIMARY KEY,
h_blob BLOB NOT NULL
h_blob BLOB NOT NULL
`

var (
Expand Down Expand Up @@ -462,7 +462,7 @@ func (m *mysql) UserUpdate(u user.User) error {
func upsertIdentities(ctx context.Context, tx *sql.Tx, ids []mysqlIdentity) error {
var sb strings.Builder
sb.WriteString("INSERT INTO " +
"identities(public_key, user_id, activated, deactivated) VALUES ")
"identities (public_key, user_id, activated, deactivated) VALUES ")

vals := make([]interface{}, 0, len(ids))
for i, id := range ids {
Expand Down Expand Up @@ -683,7 +683,7 @@ func (m *mysql) UsersGetByPubKey(pubKeys []string) (map[string]user.User, error)
//
// InsertUser satisfies the Database interface.
func (m *mysql) InsertUser(u user.User) error {
log.Tracef("UserInsert: %v", u.Username)
log.Tracef("InsertUser: %v", u.Username)

if m.isShutdown() {
return user.ErrShutdown
Expand Down Expand Up @@ -1290,7 +1290,7 @@ func (m *mysql) Close() error {
}

// New connects to a mysql instance using the given connection params,
// and returns pointer to the created mysql struct.
// and returns a pointer to the created mysql struct.
func New(host, password, network, encryptionKey string) (*mysql, error) {
// Connect to database.
dbname := databaseID + "_" + network
Expand Down
2 changes: 1 addition & 1 deletion politeiawww/legacy/user/mysql/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func TestUserUpdate(t *testing.T) {
WillReturnResult(sqlmock.NewResult(1, 1))

// Upsert user identities query
iq := "INSERT INTO identities(public_key, user_id, activated, deactivated) " +
iq := "INSERT INTO identities (public_key, user_id, activated, deactivated) " +
"VALUES ON DUPLICATE KEY UPDATE " +
"activated=VALUES(activated), deactivated=VALUES(deactivated)"

Expand Down
33 changes: 33 additions & 0 deletions politeiawww/sessions/mysql/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package mysql

import (
"github.com/decred/politeia/politeiawww/logger"
"github.com/decred/slog"
)

// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log = slog.Disabled

// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
log = slog.Disabled
}

// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using slog.
func UseLogger(logger slog.Logger) {
log = logger
}

// Initialize the package logger.
func init() {
UseLogger(logger.NewSubsystem("SESS"))
}
185 changes: 185 additions & 0 deletions politeiawww/sessions/mysql/mysql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c) 2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package mysql

import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"

"github.com/decred/politeia/politeiawww/sessions"
"github.com/pkg/errors"
)

const (
// defaultTableName is the default table name for the sessions table.
defaultTableName = "sessions"

// defaultOpTimeout is the default timeout for a single database operation.
defaultOpTimeout = 1 * time.Minute
)

// tableSessions defines the sessions table.
//
// id column is 128 bytes so that it can accomidate a 64 byte base64, base32,
// or hex encoded key.
//
// encoded_session column max length is up to 2^16 bytes which is around 64KB.
const tableSessions = `
id CHAR(128) NOT NULL PRIMARY KEY,
encoded_session BLOB NOT NULL
`

// Opts includes configurable options for the sessions database.
type Opts struct {
// TableName is the table name for the sessions table. Defaults to
// "sessions".
TableName string

// OpTimeout is the timeout for a single database operation. Defaults to
// 1 minute.
OpTimeout time.Duration
}

var (
_ sessions.DB = (*mysql)(nil)
)

// mysql implements the sessions.DB interface.
type mysql struct {
// db is the mysql DB context.
db *sql.DB

// opts includes the sessions database options.
opts *Opts
}

// ctxForOp returns a context and cancel function for a single database
// operation. It uses the database operation timeout set on the mysql
// context.
func (m *mysql) ctxForOp() (context.Context, func()) {
return context.WithTimeout(context.Background(), m.opts.OpTimeout)
}

// Save saves a session to the database.
//
// Save satisfies the sessions.DB interface.
func (m *mysql) Save(sessionID string, s sessions.EncodedSession) error {
log.Tracef("Save: %v", sessionID)

// Marshal encoded session
es, err := json.Marshal(s)
if err != nil {
return err
}

ctx, cancel := m.ctxForOp()
defer cancel()

// Save session to database
q := fmt.Sprintf(`INSERT INTO %v
(id, encoded_session) VALUES (?, ?)
ON DUPLICATE KEY UPDATE
encoded_session = VALUES(encoded_session)`, m.opts.TableName)
_, err = m.db.ExecContext(ctx, q, sessionID, es)
if err != nil {
return errors.WithStack(err)
}

return nil
}

// Del deletes a session from the database. An error is not returned if the
// session does not exist.
//
// Del satisfies the sessions.DB interface.
func (m *mysql) Del(sessionID string) error {
log.Tracef("Del: %v", sessionID)

ctx, cancel := m.ctxForOp()
defer cancel()

// Delete session
_, err := m.db.ExecContext(ctx,
"DELETE FROM "+m.opts.TableName+" WHERE id = ?", sessionID)
if err != nil {
return err
}

return nil
}

// Get gets a session from the database. An ErrNotFound error is returned if
// a session is not found for the session ID.
//
// Get statisfies the sessions.DB interface.
func (m *mysql) Get(sessionID string) (*sessions.EncodedSession, error) {
log.Tracef("Get: %v", sessionID)

ctx, cancel := m.ctxForOp()
defer cancel()

// Get session
var encodedBlob []byte
err := m.db.QueryRowContext(ctx,
"SELECT encoded_session FROM "+m.opts.TableName+" WHERE id = ?",
sessionID).Scan(&encodedBlob)
switch {
case err == sql.ErrNoRows:
return nil, sessions.ErrNotFound
case err != nil:
return nil, err
}

// Decode blob
var es sessions.EncodedSession
err = json.Unmarshal(encodedBlob, &es)
if err != nil {
return nil, err
}

return &es, nil
}

// New returns a new mysql context that implements the sessions DB interface.
// The opts param can be used to override the default mysql context settings.
func New(db *sql.DB, opts *Opts) (*mysql, error) {
// Setup database options.
tableName := defaultTableName
opTimeout := defaultOpTimeout
// Override defaults if options are provided
if opts != nil {
if opts.TableName != "" {
tableName = opts.TableName
}
if opts.OpTimeout != 0 {
opTimeout = opts.OpTimeout
}
}

// Create mysql context
m := mysql{
db: db,
opts: &Opts{
TableName: tableName,
OpTimeout: opTimeout,
},
}

ctx, cancel := m.ctxForOp()
defer cancel()

// Create sessions table
q := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %v (%v)`,
m.opts.TableName, tableSessions)
_, err := db.ExecContext(ctx, q)
if err != nil {
return nil, errors.WithStack(err)
}

return &m, nil
}
Loading

0 comments on commit fdc6e23

Please sign in to comment.