Hook up registration/login APIs and implement access token generation (#122)
This commit is contained in:
parent
65b66a6452
commit
50aacd4f3c
6 changed files with 106 additions and 17 deletions
|
@ -16,7 +16,9 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -27,6 +29,17 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
// UnknownDeviceID is the default device id if one is not specified.
|
||||
// This deviates from Synapse which generates a new device ID if one is not specified.
|
||||
// It's preferable to not amass a huge list of valid access tokens for an account,
|
||||
// so limiting it to 1 unknown device for now limits the number of valid tokens.
|
||||
// Clients should be giving us device IDs.
|
||||
var UnknownDeviceID = "unknown-device"
|
||||
|
||||
// OWASP recommends at least 128 bits of entropy for tokens: https://www.owasp.org/index.php/Insufficient_Session-ID_Length
|
||||
// 32 bytes => 256 bits
|
||||
var tokenByteLength = 32
|
||||
|
||||
// VerifyAccessToken verifies that an access token was supplied in the given HTTP request
|
||||
// and returns the device it corresponds to. Returns resErr (an error response which can be
|
||||
// sent to the client) if the token is invalid or there was a problem querying the database.
|
||||
|
@ -56,6 +69,17 @@ func VerifyAccessToken(req *http.Request, deviceDB *devices.Database) (device *a
|
|||
return
|
||||
}
|
||||
|
||||
// GenerateAccessToken creates a new access token. Returns an error if failed to generate
|
||||
// random bytes.
|
||||
func GenerateAccessToken() (string, error) {
|
||||
b := make([]byte, tokenByteLength)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// url-safe no padding
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// extractAccessToken from a request, or return an error detailing what went wrong. The
|
||||
// error message MUST be human-readable and comprehensible to the client.
|
||||
func extractAccessToken(req *http.Request) (string, error) {
|
||||
|
|
|
@ -16,6 +16,7 @@ package accounts
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
@ -44,7 +45,7 @@ func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName)
|
|||
}
|
||||
|
||||
// GetAccountByPassword returns the account associated with the given localpart and password.
|
||||
// Returns sql.ErrNoRows if no account exists which matches the given credentials.
|
||||
// Returns sql.ErrNoRows if no account exists which matches the given localpart.
|
||||
func (d *Database) GetAccountByPassword(localpart, plaintextPassword string) (*authtypes.Account, error) {
|
||||
hash, err := d.accounts.selectPasswordHash(localpart)
|
||||
if err != nil {
|
||||
|
|
|
@ -86,7 +86,7 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN
|
|||
// Returns the device on success.
|
||||
func (s *devicesStatements) insertDevice(txn *sql.Tx, id, localpart, accessToken string) (dev *authtypes.Device, err error) {
|
||||
createdTimeMS := time.Now().UnixNano() / 1000000
|
||||
if _, err = s.insertDeviceStmt.Exec(id, localpart, accessToken, createdTimeMS); err == nil {
|
||||
if _, err = txn.Stmt(s.insertDeviceStmt).Exec(id, localpart, accessToken, createdTimeMS); err == nil {
|
||||
dev = &authtypes.Device{
|
||||
ID: id,
|
||||
UserID: makeUserID(localpart, s.serverName),
|
||||
|
|
|
@ -16,12 +16,16 @@ package readers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||
"github.com/matrix-org/dendrite/clientapi/config"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type loginFlows struct {
|
||||
|
@ -52,7 +56,7 @@ func passwordLogin() loginFlows {
|
|||
}
|
||||
|
||||
// Login implements GET and POST /login
|
||||
func Login(req *http.Request, cfg config.ClientAPI) util.JSONResponse {
|
||||
func Login(req *http.Request, accountDB *accounts.Database, deviceDB *devices.Database, cfg config.ClientAPI) util.JSONResponse {
|
||||
if req.Method == "GET" { // TODO: support other forms of login other than password, depending on config options
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
|
@ -70,12 +74,41 @@ func Login(req *http.Request, cfg config.ClientAPI) util.JSONResponse {
|
|||
JSON: jsonerror.BadJSON("'user' must be supplied."),
|
||||
}
|
||||
}
|
||||
// TODO: Check username and password properly
|
||||
|
||||
util.GetLogger(req.Context()).WithField("user", r.User).Info("Processing login request")
|
||||
|
||||
acc, err := accountDB.GetAccountByPassword(r.User, r.Password)
|
||||
if err != nil {
|
||||
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows
|
||||
// but that would leak the existence of the user.
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.BadJSON("username or password was incorrect, or the account does not exist"),
|
||||
}
|
||||
}
|
||||
|
||||
token, err := auth.GenerateAccessToken()
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: 500,
|
||||
JSON: jsonerror.Unknown("Failed to generate access token"),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use the device ID in the request
|
||||
dev, err := deviceDB.CreateDevice(acc.Localpart, auth.UnknownDeviceID, token)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: 500,
|
||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: loginResponse{
|
||||
UserID: makeUserID(r.User, cfg.ServerName),
|
||||
AccessToken: makeUserID(r.User, cfg.ServerName), // FIXME: token is the user ID for now
|
||||
UserID: dev.UserID,
|
||||
AccessToken: dev.AccessToken,
|
||||
HomeServer: cfg.ServerName,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -82,14 +82,14 @@ func Setup(
|
|||
)
|
||||
|
||||
r0mux.Handle("/register", common.MakeAPI("register", func(req *http.Request) util.JSONResponse {
|
||||
return writers.Register(req, accountDB)
|
||||
return writers.Register(req, accountDB, deviceDB)
|
||||
}))
|
||||
|
||||
// Stub endpoints required by Riot
|
||||
|
||||
r0mux.Handle("/login",
|
||||
common.MakeAPI("login", func(req *http.Request) util.JSONResponse {
|
||||
return readers.Login(req, cfg)
|
||||
return readers.Login(req, accountDB, deviceDB, cfg)
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
@ -90,7 +92,7 @@ func (r *registerRequest) Validate() *util.JSONResponse {
|
|||
}
|
||||
|
||||
// Register processes a /register request. http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
|
||||
func Register(req *http.Request, accountDB *accounts.Database) util.JSONResponse {
|
||||
func Register(req *http.Request, accountDB *accounts.Database, deviceDB *devices.Database) util.JSONResponse {
|
||||
var r registerRequest
|
||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||
if resErr != nil {
|
||||
|
@ -131,7 +133,7 @@ func Register(req *http.Request, accountDB *accounts.Database) util.JSONResponse
|
|||
switch r.Auth.Type {
|
||||
case authtypes.LoginTypeDummy:
|
||||
// there is nothing to do
|
||||
return completeRegistration(accountDB, r.Username, r.Password)
|
||||
return completeRegistration(accountDB, deviceDB, r.Username, r.Password)
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: 501,
|
||||
|
@ -140,7 +142,20 @@ func Register(req *http.Request, accountDB *accounts.Database) util.JSONResponse
|
|||
}
|
||||
}
|
||||
|
||||
func completeRegistration(accountDB *accounts.Database, username, password string) util.JSONResponse {
|
||||
func completeRegistration(accountDB *accounts.Database, deviceDB *devices.Database, username, password string) util.JSONResponse {
|
||||
if username == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.BadJSON("missing username"),
|
||||
}
|
||||
}
|
||||
if password == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.BadJSON("missing password"),
|
||||
}
|
||||
}
|
||||
|
||||
acc, err := accountDB.CreateAccount(username, password)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
|
@ -148,15 +163,31 @@ func completeRegistration(accountDB *accounts.Database, username, password strin
|
|||
JSON: jsonerror.Unknown("failed to create account: " + err.Error()),
|
||||
}
|
||||
}
|
||||
// TODO: Make and store a proper access_token
|
||||
// TODO: Store the client's device information?
|
||||
|
||||
token, err := auth.GenerateAccessToken()
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: 500,
|
||||
JSON: jsonerror.Unknown("Failed to generate access token"),
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: Use the device ID in the request.
|
||||
dev, err := deviceDB.CreateDevice(username, auth.UnknownDeviceID, token)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: 500,
|
||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: registerResponse{
|
||||
UserID: acc.UserID,
|
||||
AccessToken: acc.UserID, // FIXME
|
||||
UserID: dev.UserID,
|
||||
AccessToken: dev.AccessToken,
|
||||
HomeServer: acc.ServerName,
|
||||
DeviceID: "kindauniquedeviceid",
|
||||
DeviceID: dev.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue