mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-31 13:22:46 +00:00
* Implement OpenID module (#599) - Unrelated: change Riot references to Element in client API routing Signed-off-by: Bruce MacDonald <contact@bruce-macdonald.com> * OpenID module tweaks (#599) - specify expiry is ms rather than vague ts - add OpenID token lifetime to configuration - use Go naming conventions for the path params - store plaintext token rather than hash - remove openid table sqllite mutex * Add default OpenID token lifetime (#599) * Update dendrite-config.yaml Co-authored-by: Kegsay <kegsay@gmail.com> Co-authored-by: Kegsay <kegan@matrix.org>
This commit is contained in:
parent
f8d3a762c4
commit
d27607af78
23 changed files with 553 additions and 32 deletions
|
@ -52,6 +52,8 @@ type Database interface {
|
|||
GetAccountByLocalpart(ctx context.Context, localpart string) (*api.Account, error)
|
||||
SearchProfiles(ctx context.Context, searchString string, limit int) ([]authtypes.Profile, error)
|
||||
DeactivateAccount(ctx context.Context, localpart string) (err error)
|
||||
CreateOpenIDToken(ctx context.Context, token, localpart string) (exp int64, err error)
|
||||
GetOpenIDTokenAttributes(ctx context.Context, token string) (*api.OpenIDTokenAttributes, error)
|
||||
}
|
||||
|
||||
// Err3PIDInUse is the error returned when trying to save an association involving
|
||||
|
|
84
userapi/storage/accounts/postgres/openid_table.go
Normal file
84
userapi/storage/accounts/postgres/openid_table.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const openIDTokenSchema = `
|
||||
-- Stores data about openid tokens issued for accounts.
|
||||
CREATE TABLE IF NOT EXISTS open_id_tokens (
|
||||
-- The value of the token issued to a user
|
||||
token TEXT NOT NULL PRIMARY KEY,
|
||||
-- The Matrix user ID for this account
|
||||
localpart TEXT NOT NULL,
|
||||
-- When the token expires, as a unix timestamp (ms resolution).
|
||||
token_expires_at_ms BIGINT NOT NULL
|
||||
);
|
||||
`
|
||||
|
||||
const insertTokenSQL = "" +
|
||||
"INSERT INTO open_id_tokens(token, localpart, token_expires_at_ms) VALUES ($1, $2, $3)"
|
||||
|
||||
const selectTokenSQL = "" +
|
||||
"SELECT localpart, token_expires_at_ms FROM open_id_tokens WHERE token = $1"
|
||||
|
||||
type tokenStatements struct {
|
||||
insertTokenStmt *sql.Stmt
|
||||
selectTokenStmt *sql.Stmt
|
||||
serverName gomatrixserverlib.ServerName
|
||||
}
|
||||
|
||||
func (s *tokenStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||
_, err = db.Exec(openIDTokenSchema)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if s.insertTokenStmt, err = db.Prepare(insertTokenSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectTokenStmt, err = db.Prepare(selectTokenSQL); err != nil {
|
||||
return
|
||||
}
|
||||
s.serverName = server
|
||||
return
|
||||
}
|
||||
|
||||
// insertToken inserts a new OpenID Connect token to the DB.
|
||||
// Returns new token, otherwise returns error if the token already exists.
|
||||
func (s *tokenStatements) insertToken(
|
||||
ctx context.Context,
|
||||
txn *sql.Tx,
|
||||
token, localpart string,
|
||||
expiresAtMS int64,
|
||||
) (err error) {
|
||||
stmt := sqlutil.TxStmt(txn, s.insertTokenStmt)
|
||||
_, err = stmt.ExecContext(ctx, token, localpart, expiresAtMS)
|
||||
return
|
||||
}
|
||||
|
||||
// selectOpenIDTokenAtrributes gets the attributes associated with an OpenID token from the DB
|
||||
// Returns the existing token's attributes, or err if no token is found
|
||||
func (s *tokenStatements) selectOpenIDTokenAtrributes(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
) (*api.OpenIDTokenAttributes, error) {
|
||||
var openIDTokenAttrs api.OpenIDTokenAttributes
|
||||
err := s.selectTokenStmt.QueryRowContext(ctx, token).Scan(
|
||||
&openIDTokenAttrs.UserID,
|
||||
&openIDTokenAttrs.ExpiresAtMS,
|
||||
)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
log.WithError(err).Error("Unable to retrieve token from the db")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &openIDTokenAttrs, nil
|
||||
}
|
|
@ -20,6 +20,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
|
@ -39,25 +40,28 @@ type Database struct {
|
|||
db *sql.DB
|
||||
writer sqlutil.Writer
|
||||
sqlutil.PartitionOffsetStatements
|
||||
accounts accountsStatements
|
||||
profiles profilesStatements
|
||||
accountDatas accountDataStatements
|
||||
threepids threepidStatements
|
||||
serverName gomatrixserverlib.ServerName
|
||||
bcryptCost int
|
||||
accounts accountsStatements
|
||||
profiles profilesStatements
|
||||
accountDatas accountDataStatements
|
||||
threepids threepidStatements
|
||||
openIDTokens tokenStatements
|
||||
serverName gomatrixserverlib.ServerName
|
||||
bcryptCost int
|
||||
openIDTokenLifetimeMS int64
|
||||
}
|
||||
|
||||
// NewDatabase creates a new accounts and profiles database
|
||||
func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) {
|
||||
func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, openIDTokenLifetimeMS int64) (*Database, error) {
|
||||
db, err := sqlutil.Open(dbProperties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Database{
|
||||
serverName: serverName,
|
||||
db: db,
|
||||
writer: sqlutil.NewDummyWriter(),
|
||||
bcryptCost: bcryptCost,
|
||||
serverName: serverName,
|
||||
db: db,
|
||||
writer: sqlutil.NewDummyWriter(),
|
||||
bcryptCost: bcryptCost,
|
||||
openIDTokenLifetimeMS: openIDTokenLifetimeMS,
|
||||
}
|
||||
|
||||
// Create tables before executing migrations so we don't fail if the table is missing,
|
||||
|
@ -86,6 +90,9 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
|||
if err = d.threepids.prepare(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = d.openIDTokens.prepare(db, serverName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
@ -341,3 +348,23 @@ func (d *Database) SearchProfiles(ctx context.Context, searchString string, limi
|
|||
func (d *Database) DeactivateAccount(ctx context.Context, localpart string) (err error) {
|
||||
return d.accounts.deactivateAccount(ctx, localpart)
|
||||
}
|
||||
|
||||
// CreateOpenIDToken persists a new token that was issued through OpenID Connect
|
||||
func (d *Database) CreateOpenIDToken(
|
||||
ctx context.Context,
|
||||
token, localpart string,
|
||||
) (int64, error) {
|
||||
expiresAtMS := time.Now().UnixNano()/int64(time.Millisecond) + d.openIDTokenLifetimeMS
|
||||
err := sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||
return d.openIDTokens.insertToken(ctx, txn, token, localpart, expiresAtMS)
|
||||
})
|
||||
return expiresAtMS, err
|
||||
}
|
||||
|
||||
// GetOpenIDTokenAttributes gets the attributes of issued an OIDC auth token
|
||||
func (d *Database) GetOpenIDTokenAttributes(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
) (*api.OpenIDTokenAttributes, error) {
|
||||
return d.openIDTokens.selectOpenIDTokenAtrributes(ctx, token)
|
||||
}
|
||||
|
|
86
userapi/storage/accounts/sqlite3/openid_table.go
Normal file
86
userapi/storage/accounts/sqlite3/openid_table.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const openIDTokenSchema = `
|
||||
-- Stores data about accounts.
|
||||
CREATE TABLE IF NOT EXISTS open_id_tokens (
|
||||
-- The value of the token issued to a user
|
||||
token TEXT NOT NULL PRIMARY KEY,
|
||||
-- The Matrix user ID for this account
|
||||
localpart TEXT NOT NULL,
|
||||
-- When the token expires, as a unix timestamp (ms resolution).
|
||||
token_expires_at_ms BIGINT NOT NULL
|
||||
);
|
||||
`
|
||||
|
||||
const insertTokenSQL = "" +
|
||||
"INSERT INTO open_id_tokens(token, localpart, token_expires_at_ms) VALUES ($1, $2, $3)"
|
||||
|
||||
const selectTokenSQL = "" +
|
||||
"SELECT localpart, token_expires_at_ms FROM open_id_tokens WHERE token = $1"
|
||||
|
||||
type tokenStatements struct {
|
||||
db *sql.DB
|
||||
insertTokenStmt *sql.Stmt
|
||||
selectTokenStmt *sql.Stmt
|
||||
serverName gomatrixserverlib.ServerName
|
||||
}
|
||||
|
||||
func (s *tokenStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||
s.db = db
|
||||
_, err = db.Exec(openIDTokenSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.insertTokenStmt, err = db.Prepare(insertTokenSQL); err != nil {
|
||||
return
|
||||
}
|
||||
if s.selectTokenStmt, err = db.Prepare(selectTokenSQL); err != nil {
|
||||
return
|
||||
}
|
||||
s.serverName = server
|
||||
return
|
||||
}
|
||||
|
||||
// insertToken inserts a new OpenID Connect token to the DB.
|
||||
// Returns new token, otherwise returns error if the token already exists.
|
||||
func (s *tokenStatements) insertToken(
|
||||
ctx context.Context,
|
||||
txn *sql.Tx,
|
||||
token, localpart string,
|
||||
expiresAtMS int64,
|
||||
) (err error) {
|
||||
stmt := sqlutil.TxStmt(txn, s.insertTokenStmt)
|
||||
_, err = stmt.ExecContext(ctx, token, localpart, expiresAtMS)
|
||||
return
|
||||
}
|
||||
|
||||
// selectOpenIDTokenAtrributes gets the attributes associated with an OpenID token from the DB
|
||||
// Returns the existing token's attributes, or err if no token is found
|
||||
func (s *tokenStatements) selectOpenIDTokenAtrributes(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
) (*api.OpenIDTokenAttributes, error) {
|
||||
var openIDTokenAttrs api.OpenIDTokenAttributes
|
||||
err := s.selectTokenStmt.QueryRowContext(ctx, token).Scan(
|
||||
&openIDTokenAttrs.UserID,
|
||||
&openIDTokenAttrs.ExpiresAtMS,
|
||||
)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
log.WithError(err).Error("Unable to retrieve token from the db")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &openIDTokenAttrs, nil
|
||||
}
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
|
@ -37,12 +38,14 @@ type Database struct {
|
|||
writer sqlutil.Writer
|
||||
|
||||
sqlutil.PartitionOffsetStatements
|
||||
accounts accountsStatements
|
||||
profiles profilesStatements
|
||||
accountDatas accountDataStatements
|
||||
threepids threepidStatements
|
||||
serverName gomatrixserverlib.ServerName
|
||||
bcryptCost int
|
||||
accounts accountsStatements
|
||||
profiles profilesStatements
|
||||
accountDatas accountDataStatements
|
||||
threepids threepidStatements
|
||||
openIDTokens tokenStatements
|
||||
serverName gomatrixserverlib.ServerName
|
||||
bcryptCost int
|
||||
openIDTokenLifetimeMS int64
|
||||
|
||||
accountsMu sync.Mutex
|
||||
profilesMu sync.Mutex
|
||||
|
@ -51,16 +54,17 @@ type Database struct {
|
|||
}
|
||||
|
||||
// NewDatabase creates a new accounts and profiles database
|
||||
func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (*Database, error) {
|
||||
func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, openIDTokenLifetimeMS int64) (*Database, error) {
|
||||
db, err := sqlutil.Open(dbProperties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Database{
|
||||
serverName: serverName,
|
||||
db: db,
|
||||
writer: sqlutil.NewExclusiveWriter(),
|
||||
bcryptCost: bcryptCost,
|
||||
serverName: serverName,
|
||||
db: db,
|
||||
writer: sqlutil.NewExclusiveWriter(),
|
||||
bcryptCost: bcryptCost,
|
||||
openIDTokenLifetimeMS: openIDTokenLifetimeMS,
|
||||
}
|
||||
|
||||
// Create tables before executing migrations so we don't fail if the table is missing,
|
||||
|
@ -90,6 +94,9 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
|||
if err = d.threepids.prepare(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = d.openIDTokens.prepare(db, serverName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
@ -379,3 +386,23 @@ func (d *Database) SearchProfiles(ctx context.Context, searchString string, limi
|
|||
func (d *Database) DeactivateAccount(ctx context.Context, localpart string) (err error) {
|
||||
return d.accounts.deactivateAccount(ctx, localpart)
|
||||
}
|
||||
|
||||
// CreateOpenIDToken persists a new token that was issued for OpenID Connect
|
||||
func (d *Database) CreateOpenIDToken(
|
||||
ctx context.Context,
|
||||
token, localpart string,
|
||||
) (int64, error) {
|
||||
expiresAtMS := time.Now().UnixNano()/int64(time.Millisecond) + d.openIDTokenLifetimeMS
|
||||
err := d.writer.Do(d.db, nil, func(txn *sql.Tx) error {
|
||||
return d.openIDTokens.insertToken(ctx, txn, token, localpart, expiresAtMS)
|
||||
})
|
||||
return expiresAtMS, err
|
||||
}
|
||||
|
||||
// GetOpenIDTokenAttributes gets the attributes of issued an OIDC auth token
|
||||
func (d *Database) GetOpenIDTokenAttributes(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
) (*api.OpenIDTokenAttributes, error) {
|
||||
return d.openIDTokens.selectOpenIDTokenAtrributes(ctx, token)
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ import (
|
|||
|
||||
// NewDatabase opens a new Postgres or Sqlite database (based on dataSourceName scheme)
|
||||
// and sets postgres connection parameters
|
||||
func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int) (Database, error) {
|
||||
func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserverlib.ServerName, bcryptCost int, openIDTokenLifetimeMS int64) (Database, error) {
|
||||
switch {
|
||||
case dbProperties.ConnectionString.IsSQLite():
|
||||
return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost)
|
||||
return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost, openIDTokenLifetimeMS)
|
||||
case dbProperties.ConnectionString.IsPostgres():
|
||||
return postgres.NewDatabase(dbProperties, serverName, bcryptCost)
|
||||
return postgres.NewDatabase(dbProperties, serverName, bcryptCost, openIDTokenLifetimeMS)
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected database type")
|
||||
}
|
||||
|
|
|
@ -26,10 +26,11 @@ func NewDatabase(
|
|||
dbProperties *config.DatabaseOptions,
|
||||
serverName gomatrixserverlib.ServerName,
|
||||
bcryptCost int,
|
||||
openIDTokenLifetimeMS int64,
|
||||
) (Database, error) {
|
||||
switch {
|
||||
case dbProperties.ConnectionString.IsSQLite():
|
||||
return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost)
|
||||
return sqlite3.NewDatabase(dbProperties, serverName, bcryptCost, openIDTokenLifetimeMS)
|
||||
case dbProperties.ConnectionString.IsPostgres():
|
||||
return nil, fmt.Errorf("can't use Postgres implementation")
|
||||
default:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue