mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-08-02 06:12:45 +00:00
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:
parent
426a0365cf
commit
9d4d18ae7f
11 changed files with 1492 additions and 0 deletions
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue