Improve web layer authentication and session process

This commit is contained in:
Tobie Morgan Hitchcock 2017-11-16 20:52:17 +00:00
parent 1a5267e2e2
commit 2609d761be
4 changed files with 376 additions and 166 deletions

View file

@ -23,6 +23,10 @@ var Settings *Options
type Kind int type Kind int
func (k Kind) MarshalText() (data []byte, err error) {
return []byte(k.String()), err
}
func (k Kind) String() string { func (k Kind) String() string {
switch k { switch k {
default: default:
@ -53,7 +57,8 @@ const (
type Auth struct { type Auth struct {
Kind Kind Kind Kind
Data map[string]interface{} Data interface{}
Scope string
Possible struct { Possible struct {
NS string NS string
DB string DB string

View file

@ -28,10 +28,26 @@ import (
"github.com/abcum/surreal/db" "github.com/abcum/surreal/db"
"github.com/abcum/surreal/kvs" "github.com/abcum/surreal/kvs"
"github.com/abcum/surreal/mem" "github.com/abcum/surreal/mem"
"github.com/abcum/surreal/sql"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
const (
varKeyIp = "ip"
varKeyNs = "NS"
varKeyDb = "DB"
varKeySc = "SC"
varKeyTk = "TK"
varKeyUs = "US"
varKeyTb = "TB"
varKeyId = "ID"
varKeyAuth = "auth"
varKeyUser = "user"
varKeyPass = "pass"
varKeyOrigin = "origin"
)
func cidr(ip net.IP, networks []*net.IPNet) bool { func cidr(ip net.IP, networks []*net.IPNet) bool {
for _, network := range networks { for _, network := range networks {
if network.Contains(ip) { if network.Contains(ip) {
@ -46,13 +62,7 @@ func auth() fibre.MiddlewareFunc {
return func(c *fibre.Context) (err error) { return func(c *fibre.Context) (err error) {
auth := &cnf.Auth{} auth := &cnf.Auth{}
c.Set("auth", auth) c.Set(varKeyAuth, auth)
// Ensure that the authentication data
// object is initiated at the beginning
// so it is present when serialized.
auth.Data = make(map[string]interface{})
// Start off with an authentication level // Start off with an authentication level
// which prevents running any sql queries, // which prevents running any sql queries,
@ -77,7 +87,6 @@ func auth() fibre.MiddlewareFunc {
subs := strings.Split(bits[0], "-") subs := strings.Split(bits[0], "-")
if len(subs) == 2 { if len(subs) == 2 {
auth.Kind = cnf.AuthSC
auth.Possible.NS = subs[0] auth.Possible.NS = subs[0]
auth.Selected.NS = subs[0] auth.Selected.NS = subs[0]
auth.Possible.DB = subs[1] auth.Possible.DB = subs[1]
@ -88,8 +97,7 @@ func auth() fibre.MiddlewareFunc {
// the request headers, then mark it as // the request headers, then mark it as
// the selected namespace. // the selected namespace.
if ns := c.Request().Header().Get("NS"); len(ns) != 0 { if ns := c.Request().Header().Get(varKeyNs); len(ns) != 0 {
auth.Kind = cnf.AuthSC
auth.Possible.NS = ns auth.Possible.NS = ns
auth.Selected.NS = ns auth.Selected.NS = ns
} }
@ -98,8 +106,7 @@ func auth() fibre.MiddlewareFunc {
// the request headers, then mark it as // the request headers, then mark it as
// the selected database. // the selected database.
if db := c.Request().Header().Get("DB"); len(db) != 0 { if db := c.Request().Header().Get(varKeyDb); len(db) != 0 {
auth.Kind = cnf.AuthSC
auth.Possible.DB = db auth.Possible.DB = db
auth.Selected.DB = db auth.Selected.DB = db
} }
@ -117,7 +124,6 @@ func auth() fibre.MiddlewareFunc {
if len(head) == 0 { if len(head) == 0 {
for _, prot := range websocket.Subprotocols(c.Request().Request) { for _, prot := range websocket.Subprotocols(c.Request().Request) {
if len(prot) > 7 && prot[0:7] == "bearer-" { if len(prot) > 7 && prot[0:7] == "bearer-" {
head = "Bearer " + prot[7:]
return checkBearer(c, prot[7:], func() error { return checkBearer(c, prot[7:], func() error {
return h(c) return h(c)
}) })
@ -129,8 +135,8 @@ func auth() fibre.MiddlewareFunc {
// is a Basic Auth header, and if it is then // is a Basic Auth header, and if it is then
// process this as root authentication. // process this as root authentication.
if len(head) > 0 && head[:5] == "Basic" { if len(head) > 6 && head[:5] == "Basic" {
return checkMaster(c, head[6:], func() error { return checkBasics(c, head[6:], func() error {
return h(c) return h(c)
}) })
} }
@ -139,7 +145,7 @@ func auth() fibre.MiddlewareFunc {
// is a Bearer Auth header, and if it is then // is a Bearer Auth header, and if it is then
// process this as default authentication. // process this as default authentication.
if len(head) > 0 && head[:6] == "Bearer" { if len(head) > 7 && head[:6] == "Bearer" {
return checkBearer(c, head[7:], func() error { return checkBearer(c, head[7:], func() error {
return h(c) return h(c)
}) })
@ -151,56 +157,90 @@ func auth() fibre.MiddlewareFunc {
} }
} }
func checkRoot(c *fibre.Context, user, pass string, callback func() error) (err error) { func checkBasics(c *fibre.Context, info string, callback func() error) (err error) {
auth := c.Get("auth").(*cnf.Auth) var base []byte
var cred [][]byte
if cidr(c.IP(), cnf.Settings.Auth.Nets) { auth := c.Get(varKeyAuth).(*cnf.Auth)
if user == cnf.Settings.Auth.User && pass == cnf.Settings.Auth.Pass {
auth.Kind = cnf.AuthKV
auth.Possible.NS = "*"
auth.Possible.DB = "*"
}
}
return callback()
}
func checkMaster(c *fibre.Context, info string, callback func() error) (err error) {
auth := c.Get("auth").(*cnf.Auth)
user := []byte(cnf.Settings.Auth.User) user := []byte(cnf.Settings.Auth.User)
pass := []byte(cnf.Settings.Auth.Pass) pass := []byte(cnf.Settings.Auth.Pass)
base, err := base64.StdEncoding.DecodeString(info) // Parse the base64 encoded basic auth data
if err == nil && cidr(c.IP(), cnf.Settings.Auth.Nets) { if base, err = base64.StdEncoding.DecodeString(info); err != nil {
return fibre.NewHTTPError(401).WithMessage("Problem with basic auth data")
}
cred := bytes.SplitN(base, []byte(":"), 2) // Split the basic auth USER and PASS details
if len(cred) == 2 && bytes.Equal(cred[0], user) && bytes.Equal(cred[1], pass) { if cred = bytes.SplitN(base, []byte(":"), 2); len(cred) != 2 {
return fibre.NewHTTPError(401).WithMessage("Problem with basic auth data")
}
// Check to see if IP, USER, and PASS match server settings
if bytes.Equal(cred[0], user) && bytes.Equal(cred[1], pass) {
if cidr(c.IP(), cnf.Settings.Auth.Nets) {
auth.Kind = cnf.AuthKV auth.Kind = cnf.AuthKV
auth.Possible.NS = "*" auth.Possible.NS = "*"
auth.Possible.DB = "*" auth.Possible.DB = "*"
return callback()
}
return fibre.NewHTTPError(403).WithMessage("IP invalid for root authentication")
}
// If no KV authentication, then try to authenticate as NS user
if auth.Selected.NS != "" {
n := auth.Selected.NS
u := string(cred[0])
p := string(cred[1])
if _, err = signinNS(n, u, p); err == nil {
auth.Kind = cnf.AuthNS
auth.Possible.NS = n
auth.Possible.DB = "*"
return callback()
}
// If no NS authentication, then try to authenticate as DB user
if auth.Selected.DB != "" {
n := auth.Selected.NS
d := auth.Selected.DB
u := string(cred[0])
p := string(cred[1])
if _, err = signinDB(n, d, u, p); err == nil {
auth.Kind = cnf.AuthDB
auth.Possible.NS = n
auth.Possible.DB = d
return callback()
}
} }
} }
return callback() return fibre.NewHTTPError(401).WithMessage("Invalid authentication details")
} }
func checkBearer(c *fibre.Context, info string, callback func() error) (err error) { func checkBearer(c *fibre.Context, info string, callback func() error) (err error) {
auth := c.Get("auth").(*cnf.Auth) auth := c.Get(varKeyAuth).(*cnf.Auth)
var txn kvs.TX var txn kvs.TX
var res []*db.Response
var vars jwt.MapClaims var vars jwt.MapClaims
var nok, dok, sok, tok, uok bool var nsk, dbk, sck, tkk, usk, tbk, idk bool
var nsv, dbv, scv, tkv, usv string var nsv, dbv, scv, tkv, usv, tbv, idv string
// Start a new read transaction. // Start a new read transaction.
@ -212,6 +252,10 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
defer txn.Cancel() defer txn.Cancel()
// Setup the kvs layer cache.
cache := mem.NewWithTX(txn)
// Parse the specified JWT Token. // Parse the specified JWT Token.
token, err := jwt.Parse(info, func(token *jwt.Token) (interface{}, error) { token, err := jwt.Parse(info, func(token *jwt.Token) (interface{}, error) {
@ -222,15 +266,13 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
return nil, err return nil, err
} }
if val, ok := vars["auth"].(map[string]interface{}); ok { nsv, nsk = vars[varKeyNs].(string) // Namespace
auth.Data = val dbv, dbk = vars[varKeyDb].(string) // Database
} scv, sck = vars[varKeySc].(string) // Scope
tkv, tkk = vars[varKeyTk].(string) // Token
nsv, nok = vars["NS"].(string) // Namespace usv, usk = vars[varKeyUs].(string) // Login
dbv, dok = vars["DB"].(string) // Database tbv, tbk = vars[varKeyTb].(string) // Table
scv, sok = vars["SC"].(string) // Scope idv, idk = vars[varKeyId].(string) // Thing
tkv, tok = vars["TK"].(string) // Token
usv, uok = vars["US"].(string) // Login
if tkv == "default" { if tkv == "default" {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
@ -238,17 +280,53 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
} }
} }
if nok && dok && sok && tok { if nsk && dbk && sck && tkk {
scp, err := mem.New(txn).GetSC(nsv, dbv, scv) scp, err := cache.GetSC(nsv, dbv, scv)
if err != nil { if err != nil {
return nil, fmt.Errorf("Credentials failed") return nil, fmt.Errorf("Credentials failed")
} }
auth.Data["scope"] = scp.Name // Store the authenticated scope.
auth.Scope = scp.Name.ID
// Store the authenticated thing.
auth.Data = sql.NewThing(tbv, idv)
// Check that the scope specifies connect.
if exp, ok := scp.Connect.(*sql.SubExpression); ok {
// Process the scope connect statement.
c := fibre.NewContext(c.Request(), c.Response(), c.Fibre())
c.Set(varKeyAuth, &cnf.Auth{Kind: cnf.AuthDB})
qvars := map[string]interface{}{"id": auth.Data}
query := &sql.Query{Statements: []sql.Statement{exp.Expr}}
// If the query fails then fail authentication.
if res, err = db.Process(c, query, qvars); err != nil {
return nil, fmt.Errorf("Credentials failed")
}
// If the response is not 1 record then fail authentication.
if len(res) != 1 || len(res[0].Result) != 1 {
return nil, fmt.Errorf("Credentials failed")
}
auth.Data = res[0].Result[0]
}
if tkv != "default" { if tkv != "default" {
key, err := mem.New(txn).GetST(nsv, dbv, scv, tkv) key, err := cache.GetST(nsv, dbv, scv, tkv)
if err != nil { if err != nil {
return nil, fmt.Errorf("Credentials failed") return nil, fmt.Errorf("Credentials failed")
} }
@ -262,10 +340,10 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
return scp.Code, nil return scp.Code, nil
} }
} else if nok && dok && tok { } else if nsk && dbk && tkk {
if tkv != "default" { if tkv != "default" {
key, err := mem.New(txn).GetDT(nsv, dbv, tkv) key, err := cache.GetDT(nsv, dbv, tkv)
if err != nil { if err != nil {
return nil, fmt.Errorf("Credentials failed") return nil, fmt.Errorf("Credentials failed")
} }
@ -274,8 +352,8 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
} }
auth.Kind = cnf.AuthDB auth.Kind = cnf.AuthDB
return key.Code, nil return key.Code, nil
} else if uok { } else if usk {
usr, err := mem.New(txn).GetDU(nsv, dbv, usv) usr, err := cache.GetDU(nsv, dbv, usv)
if err != nil { if err != nil {
return nil, fmt.Errorf("Credentials failed") return nil, fmt.Errorf("Credentials failed")
} }
@ -283,10 +361,10 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
return usr.Code, nil return usr.Code, nil
} }
} else if nok && tok { } else if nsk && tkk {
if tkv != "default" { if tkv != "default" {
key, err := mem.New(txn).GetNT(nsv, tkv) key, err := cache.GetNT(nsv, tkv)
if err != nil { if err != nil {
return nil, fmt.Errorf("Credentials failed") return nil, fmt.Errorf("Credentials failed")
} }
@ -295,8 +373,8 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
} }
auth.Kind = cnf.AuthNS auth.Kind = cnf.AuthNS
return key.Code, nil return key.Code, nil
} else if uok { } else if usk {
usr, err := mem.New(txn).GetNU(nsv, usv) usr, err := cache.GetNU(nsv, usv)
if err != nil { if err != nil {
return nil, fmt.Errorf("Credentials failed") return nil, fmt.Errorf("Credentials failed")
} }
@ -332,8 +410,10 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro
auth.Selected.DB = dbv auth.Selected.DB = dbv
} }
return callback()
} }
return callback() return fibre.NewHTTPError(401).WithMessage("Invalid authentication details")
} }

View file

@ -18,11 +18,12 @@ import (
"time" "time"
"github.com/abcum/fibre" "github.com/abcum/fibre"
"github.com/abcum/surreal/cnf"
"github.com/abcum/surreal/db" "github.com/abcum/surreal/db"
"github.com/abcum/surreal/kvs" "github.com/abcum/surreal/kvs"
"github.com/abcum/surreal/mem" "github.com/abcum/surreal/mem"
"github.com/abcum/surreal/sql" "github.com/abcum/surreal/sql"
"github.com/abcum/surreal/util/data"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -33,9 +34,21 @@ func signin(c *fibre.Context) (err error) {
c.Bind(&vars) c.Bind(&vars)
n, nok := vars["NS"].(string) n, nok := vars[varKeyNs].(string)
d, dok := vars["DB"].(string) d, dok := vars[varKeyDb].(string)
s, sok := vars["SC"].(string) s, sok := vars[varKeySc].(string)
// Ensure that the IP address of the
// user signing in is available so that
// it can be used within signin queries.
vars[varKeyIp] = c.IP().String()
// Ensure that the website origin of the
// user signing in is available so that
// it can be used within signin queries.
vars[varKeyOrigin] = c.Origin()
// If we have a namespace, database, and // If we have a namespace, database, and
// scope defined, then we are logging in // scope defined, then we are logging in
@ -43,9 +56,12 @@ func signin(c *fibre.Context) (err error) {
if nok && len(n) > 0 && dok && len(d) > 0 && sok && len(s) > 0 { if nok && len(n) > 0 && dok && len(d) > 0 && sok && len(s) > 0 {
var ok bool
var txn kvs.TX var txn kvs.TX
var str string var str string
var doc *sql.Thing
var res []*db.Response var res []*db.Response
var exp *sql.SubExpression
var scp *sql.DefineScopeStatement var scp *sql.DefineScopeStatement
// Start a new read transaction. // Start a new read transaction.
@ -60,7 +76,7 @@ func signin(c *fibre.Context) (err error) {
// Get the specified signin scope. // Get the specified signin scope.
if scp, err = mem.New(txn).GetSC(n, d, s); err != nil { if scp, err = mem.NewWithTX(txn).GetSC(n, d, s); err != nil {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"db": d, "db": d,
@ -68,11 +84,25 @@ func signin(c *fibre.Context) (err error) {
}).WithMessage("Authentication scope does not exist") }).WithMessage("Authentication scope does not exist")
} }
// Check that the scope allows signin.
if exp, ok = scp.Signin.(*sql.SubExpression); !ok {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"sc": s,
}).WithMessage("Authentication scope signup was unsuccessful")
}
// Process the scope signin statement. // Process the scope signin statement.
qury := &sql.Query{Statements: []sql.Statement{scp.Signup}} c.Set(varKeyAuth, &cnf.Auth{Kind: cnf.AuthDB})
if res, err = db.Process(c, qury, vars); err != nil { query := &sql.Query{Statements: []sql.Statement{exp.Expr}}
// If the query fails then return a 501 error.
if res, err = db.Process(c, query, vars); err != nil {
return fibre.NewHTTPError(501).WithFields(map[string]interface{}{ return fibre.NewHTTPError(501).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"db": d, "db": d,
@ -80,7 +110,19 @@ func signin(c *fibre.Context) (err error) {
}).WithMessage("Authentication scope signin was unsuccessful") }).WithMessage("Authentication scope signin was unsuccessful")
} }
if len(res) != 1 && len(res[0].Result) != 1 { // If the response is not 1 record then return a 403 error.
if len(res) != 1 || len(res[0].Result) != 1 {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"sc": s,
}).WithMessage("Authentication scope signin was unsuccessful")
}
// If the query does not return an id field then return a 403 error.
if doc, ok = data.Consume(res[0].Result[0]).Get("id").Data().(*sql.Thing); !ok {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"db": d, "db": d,
@ -91,15 +133,16 @@ func signin(c *fibre.Context) (err error) {
// Create a new token signer with the default claims. // Create a new token signer with the default claims.
signr := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{ signr := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
"NS": n, "NS": n,
"DB": d, "DB": d,
"SC": s, "SC": s,
"TK": "default", "TK": "default",
"iss": "Surreal", "iss": "Surreal",
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"nbf": time.Now().Unix(), "nbf": time.Now().Unix(),
"exp": time.Now().Add(scp.Time).Unix(), "exp": time.Now().Add(scp.Time).Unix(),
"auth": res[0].Result[0], "TB": doc.TB,
"ID": doc.ID,
}) })
// Try to create the final signed token as a string. // Try to create the final signed token as a string.
@ -112,7 +155,7 @@ func signin(c *fibre.Context) (err error) {
}).WithMessage("Problem with signing string") }).WithMessage("Problem with signing string")
} }
return c.Text(200, str) return c.Send(200, str)
} }
@ -122,16 +165,15 @@ func signin(c *fibre.Context) (err error) {
if nok && len(n) > 0 && dok && len(d) > 0 { if nok && len(n) > 0 && dok && len(d) > 0 {
var txn kvs.TX
var str string var str string
var usr *sql.DefineLoginStatement var usr *sql.DefineLoginStatement
// Get the specified user and password. // Get the specified user and password.
u, uok := vars["user"].(string) u, uok := vars[varKeyUser].(string)
p, pok := vars["pass"].(string) p, pok := vars[varKeyPass].(string)
if !uok || len(u) == 0 || !pok || len(p) == 0 { if !uok || !pok {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"db": d, "db": d,
@ -141,32 +183,8 @@ func signin(c *fibre.Context) (err error) {
// Start a new read transaction. // Start a new read transaction.
if txn, err = db.Begin(false); err != nil { if usr, err = signinDB(n, d, u, p); err != nil {
return fibre.NewHTTPError(500) return err
}
// Ensure the transaction closes.
defer txn.Cancel()
// Get the specified database login.
if usr, err = mem.New(txn).GetDU(n, d, u); err != nil {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"du": u,
}).WithMessage("Database login does not exist")
}
// Compare the hashed and stored passwords.
if err = bcrypt.CompareHashAndPassword(usr.Pass, []byte(p)); err != nil {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"du": u,
}).WithMessage("Database signin was unsuccessful")
} }
// Create a new token signer with the default claims. // Create a new token signer with the default claims.
@ -189,10 +207,10 @@ func signin(c *fibre.Context) (err error) {
"ns": n, "ns": n,
"db": d, "db": d,
"du": u, "du": u,
}).WithMessage("Problem with singing string") }).WithMessage("Problem with signing string")
} }
return c.Text(200, str) return c.Send(200, str)
} }
@ -202,48 +220,23 @@ func signin(c *fibre.Context) (err error) {
if nok && len(n) > 0 { if nok && len(n) > 0 {
var txn kvs.TX
var str string var str string
var usr *sql.DefineLoginStatement var usr *sql.DefineLoginStatement
// Get the specified user and password. // Get the specified user and password.
u, uok := vars["user"].(string) u, uok := vars[varKeyUser].(string)
p, pok := vars["pass"].(string) p, pok := vars[varKeyPass].(string)
if !uok || len(u) == 0 || !pok || len(p) == 0 { if !uok || !pok {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"nu": u, "nu": u,
}).WithMessage("Database signin was unsuccessful") }).WithMessage("Database signin was unsuccessful")
} }
// Start a new read transaction. if usr, err = signinNS(n, u, p); err != nil {
return err
if txn, err = db.Begin(false); err != nil {
return fibre.NewHTTPError(500)
}
// Ensure the transaction closes.
defer txn.Cancel()
// Get the specified namespace login.
if usr, err = mem.New(txn).GetNU(n, u); err != nil {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Namespace login does not exist")
}
// Compare the hashed and stored passwords.
if err = bcrypt.CompareHashAndPassword(usr.Pass, []byte(p)); err != nil {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Namespace signin was unsuccessful")
} }
// Create a new token signer with the default claims. // Create a new token signer with the default claims.
@ -264,13 +257,103 @@ func signin(c *fibre.Context) (err error) {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"nu": u, "nu": u,
}).WithMessage("Problem with singing string") }).WithMessage("Problem with signing string")
} }
return c.Text(200, str) return c.Send(200, str)
} }
return fibre.NewHTTPError(403) return fibre.NewHTTPError(403)
} }
func signinDB(n, d, u, p string) (usr *sql.DefineLoginStatement, err error) {
var txn kvs.TX
// Start a new read transaction.
if txn, err = db.Begin(false); err != nil {
return nil, fibre.NewHTTPError(500)
}
// Ensure the transaction closes.
defer txn.Cancel()
// Get the specified user and password.
if len(u) == 0 || len(p) == 0 {
return nil, fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Database signin was unsuccessful")
}
// Get the specified namespace login.
if usr, err = mem.NewWithTX(txn).GetDU(n, d, u); err != nil {
return nil, fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Database login does not exist")
}
// Compare the hashed and stored passwords.
if err = bcrypt.CompareHashAndPassword(usr.Pass, []byte(p)); err != nil {
return nil, fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Database signin was unsuccessful")
}
return
}
func signinNS(n, u, p string) (usr *sql.DefineLoginStatement, err error) {
var txn kvs.TX
// Start a new read transaction.
if txn, err = db.Begin(false); err != nil {
return nil, fibre.NewHTTPError(500)
}
// Ensure the transaction closes.
defer txn.Cancel()
// Get the specified user and password.
if len(u) == 0 || len(p) == 0 {
return nil, fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Database signin was unsuccessful")
}
// Get the specified namespace login.
if usr, err = mem.NewWithTX(txn).GetNU(n, u); err != nil {
return nil, fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Namespace login does not exist")
}
// Compare the hashed and stored passwords.
if err = bcrypt.CompareHashAndPassword(usr.Pass, []byte(p)); err != nil {
return nil, fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"nu": u,
}).WithMessage("Namespace signin was unsuccessful")
}
return
}

View file

@ -16,10 +16,12 @@ package web
import ( import (
"github.com/abcum/fibre" "github.com/abcum/fibre"
"github.com/abcum/surreal/cnf"
"github.com/abcum/surreal/db" "github.com/abcum/surreal/db"
"github.com/abcum/surreal/kvs" "github.com/abcum/surreal/kvs"
"github.com/abcum/surreal/mem" "github.com/abcum/surreal/mem"
"github.com/abcum/surreal/sql" "github.com/abcum/surreal/sql"
"github.com/abcum/surreal/util/data"
) )
func signup(c *fibre.Context) (err error) { func signup(c *fibre.Context) (err error) {
@ -28,9 +30,21 @@ func signup(c *fibre.Context) (err error) {
c.Bind(&vars) c.Bind(&vars)
n, nok := vars["NS"].(string) n, nok := vars[varKeyNs].(string)
d, dok := vars["DB"].(string) d, dok := vars[varKeyDb].(string)
s, sok := vars["SC"].(string) s, sok := vars[varKeySc].(string)
// Ensure that the IP address of the
// user signing up is available so that
// it can be used within signup queries.
vars[varKeyIp] = c.IP().String()
// Ensure that the website origin of the
// user signing up is available so that
// it can be used within signup queries.
vars[varKeyOrigin] = c.Origin()
// If we have a namespace, database, and // If we have a namespace, database, and
// scope defined, then we are logging in // scope defined, then we are logging in
@ -38,8 +52,10 @@ func signup(c *fibre.Context) (err error) {
if nok && len(n) > 0 && dok && len(d) > 0 && sok && len(s) > 0 { if nok && len(n) > 0 && dok && len(d) > 0 && sok && len(s) > 0 {
var ok bool
var txn kvs.TX var txn kvs.TX
var res []*db.Response var res []*db.Response
var exp *sql.SubExpression
var scp *sql.DefineScopeStatement var scp *sql.DefineScopeStatement
// Start a new read transaction. // Start a new read transaction.
@ -54,7 +70,7 @@ func signup(c *fibre.Context) (err error) {
// Get the specified signin scope. // Get the specified signin scope.
if scp, err = mem.New(txn).GetSC(n, d, s); err != nil { if scp, err = mem.NewWithTX(txn).GetSC(n, d, s); err != nil {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"db": d, "db": d,
@ -62,19 +78,9 @@ func signup(c *fibre.Context) (err error) {
}).WithMessage("Authentication scope does not exist") }).WithMessage("Authentication scope does not exist")
} }
// Process the scope signup statement. // Check that the scope allows signup.
qury := &sql.Query{Statements: []sql.Statement{scp.Signup}} if exp, ok = scp.Signup.(*sql.SubExpression); !ok {
if res, err = db.Process(c, qury, vars); err != nil {
return fibre.NewHTTPError(501).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"sc": s,
}).WithMessage("Authentication scope signup was unsuccessful")
}
if len(res) != 1 && len(res[0].Result) != 1 {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n, "ns": n,
"db": d, "db": d,
@ -82,10 +88,46 @@ func signup(c *fibre.Context) (err error) {
}).WithMessage("Authentication scope signup was unsuccessful") }).WithMessage("Authentication scope signup was unsuccessful")
} }
return c.Code(200) // Process the scope signup statement.
c.Set(varKeyAuth, &cnf.Auth{Kind: cnf.AuthDB})
query := &sql.Query{Statements: []sql.Statement{exp.Expr}}
// If the query fails then return a 501 error.
if res, err = db.Process(c, query, vars); err != nil {
return fibre.NewHTTPError(501).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"sc": s,
}).WithMessage("Authentication scope signup was unsuccessful")
}
// If the response is not 1 record then return a 403 error.
if len(res) != 1 || len(res[0].Result) != 1 {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"sc": s,
}).WithMessage("Authentication scope signup was unsuccessful")
}
// If the query does not return an id field then return a 403 error.
if _, ok = data.Consume(res[0].Result[0]).Get("id").Data().(*sql.Thing); !ok {
return fibre.NewHTTPError(403).WithFields(map[string]interface{}{
"ns": n,
"db": d,
"sc": s,
}).WithMessage("Authentication scope signup was unsuccessful")
}
return c.Code(204)
} }
return fibre.NewHTTPError(401) return fibre.NewHTTPError(403)
} }