Add AccountDatabase for storing user accounts (#110)

Including the ability to add new accounts with a user/password and
select accounts using a user/password. Uses bcrypt to hash passwords.
This commit is contained in:
Kegsay 2017-05-19 10:27:03 +01:00 committed by GitHub
parent 426a0365cf
commit 9d4d18ae7f
11 changed files with 1492 additions and 0 deletions

View file

@ -0,0 +1,106 @@
// Copyright 2017 Vector Creations Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"database/sql"
"fmt"
"time"
"github.com/matrix-org/dendrite/clientapi/auth/types"
"github.com/matrix-org/gomatrixserverlib"
)
const accountsSchema = `
-- Stores data about accounts.
CREATE TABLE IF NOT EXISTS accounts (
-- The Matrix user ID localpart for this account
localpart TEXT NOT NULL PRIMARY KEY,
-- When this account was first created, as a unix timestamp (ms resolution).
created_ts BIGINT NOT NULL,
-- The password hash for this account. Can be NULL if this is a passwordless account.
password_hash TEXT
-- TODO:
-- is_guest, is_admin, appservice_id, upgraded_ts, devices, any email reset stuff?
);
`
const insertAccountSQL = "" +
"INSERT INTO accounts(localpart, created_ts, password_hash) VALUES ($1, $2, $3)"
const selectAccountByLocalpartSQL = "" +
"SELECT localpart WHERE localpart = $1"
const selectPasswordHashSQL = "" +
"SELECT password_hash WHERE localpart = $1"
// TODO: Update password
type accountsStatements struct {
insertAccountStmt *sql.Stmt
selectAccountByLocalpartStmt *sql.Stmt
selectPasswordHashStmt *sql.Stmt
serverName gomatrixserverlib.ServerName
}
func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
_, err = db.Exec(accountsSchema)
if err != nil {
return
}
if s.insertAccountStmt, err = db.Prepare(insertAccountSQL); err != nil {
return
}
if s.selectAccountByLocalpartStmt, err = db.Prepare(selectAccountByLocalpartSQL); err != nil {
return
}
if s.selectPasswordHashStmt, err = db.Prepare(selectPasswordHashSQL); err != nil {
return
}
s.serverName = server
return
}
// insertAccount creates a new account. 'hash' should be the password hash for this account. If it is missing,
// this account will be passwordless. Returns an error if this account already exists. Returns the account
// on success.
func (s *accountsStatements) insertAccount(localpart, hash string) (acc *types.Account, err error) {
createdTimeMS := time.Now().UnixNano() / 1000000
if _, err = s.insertAccountStmt.Exec(localpart, createdTimeMS, hash); err != nil {
acc = &types.Account{
Localpart: localpart,
UserID: makeUserID(localpart, s.serverName),
}
}
return
}
func (s *accountsStatements) selectPasswordHash(localpart string) (hash string, err error) {
err = s.selectPasswordHashStmt.QueryRow(localpart).Scan(&hash)
return
}
func (s *accountsStatements) selectAccountByLocalpart(localpart string) (*types.Account, error) {
var acc types.Account
err := s.selectAccountByLocalpartStmt.QueryRow(localpart).Scan(&acc.Localpart)
if err != nil {
acc.UserID = makeUserID(localpart, s.serverName)
}
return &acc, err
}
func makeUserID(localpart string, server gomatrixserverlib.ServerName) string {
return fmt.Sprintf("@%s:%s", localpart, string(server))
}

View file

@ -0,0 +1,70 @@
// Copyright 2017 Vector Creations Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"database/sql"
"github.com/matrix-org/dendrite/clientapi/auth/types"
"github.com/matrix-org/gomatrixserverlib"
"golang.org/x/crypto/bcrypt"
)
// AccountDatabase represents an account database
type AccountDatabase struct {
db *sql.DB
accounts accountsStatements
}
// NewAccountDatabase creates a new accounts database
func NewAccountDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) (*AccountDatabase, error) {
var db *sql.DB
var err error
if db, err = sql.Open("postgres", dataSourceName); err != nil {
return nil, err
}
a := accountsStatements{}
if err = a.prepare(db, serverName); err != nil {
return nil, err
}
return &AccountDatabase{db, a}, nil
}
// GetAccountByPassword returns the account associated with the given localpart and password.
// Returns sql.ErrNoRows if no account exists which matches the given credentials.
func (d *AccountDatabase) GetAccountByPassword(localpart, plaintextPassword string) (*types.Account, error) {
hash, err := d.accounts.selectPasswordHash(localpart)
if err != nil {
return nil, err
}
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintextPassword)); err != nil {
return nil, err
}
return d.accounts.selectAccountByLocalpart(localpart)
}
// CreateAccount makes a new account with the given login name and password. If no password is supplied,
// the account will be a passwordless account.
func (d *AccountDatabase) CreateAccount(localpart, plaintextPassword string) (*types.Account, error) {
hash, err := hashPassword(plaintextPassword)
if err != nil {
return nil, err
}
return d.accounts.insertAccount(localpart, hash)
}
func hashPassword(plaintext string) (hash string, err error) {
hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost)
return string(hashBytes), err
}

View file

@ -0,0 +1,24 @@
// Copyright 2017 Vector Creations Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
// Account represents a Matrix account on this home server.
type Account struct {
UserID string
Localpart string
// TODO: Other flags like IsAdmin, IsGuest
// TODO: Device IDs
// TODO: Associations (e.g. with application services)
}