From 2609d761be5de7fa0781718b34d886b6b0d9e046 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Thu, 16 Nov 2017 20:52:17 +0000 Subject: [PATCH] Improve web layer authentication and session process --- cnf/cnf.go | 7 +- web/auth.go | 210 +++++++++++++++++++++++++++++------------- web/signin.go | 247 +++++++++++++++++++++++++++++++++----------------- web/signup.go | 78 ++++++++++++---- 4 files changed, 376 insertions(+), 166 deletions(-) diff --git a/cnf/cnf.go b/cnf/cnf.go index 42423dcd..15619b57 100644 --- a/cnf/cnf.go +++ b/cnf/cnf.go @@ -23,6 +23,10 @@ var Settings *Options type Kind int +func (k Kind) MarshalText() (data []byte, err error) { + return []byte(k.String()), err +} + func (k Kind) String() string { switch k { default: @@ -53,7 +57,8 @@ const ( type Auth struct { Kind Kind - Data map[string]interface{} + Data interface{} + Scope string Possible struct { NS string DB string diff --git a/web/auth.go b/web/auth.go index 21dbe511..6543f507 100644 --- a/web/auth.go +++ b/web/auth.go @@ -28,10 +28,26 @@ import ( "github.com/abcum/surreal/db" "github.com/abcum/surreal/kvs" "github.com/abcum/surreal/mem" + "github.com/abcum/surreal/sql" "github.com/dgrijalva/jwt-go" "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 { for _, network := range networks { if network.Contains(ip) { @@ -46,13 +62,7 @@ func auth() fibre.MiddlewareFunc { return func(c *fibre.Context) (err error) { auth := &cnf.Auth{} - c.Set("auth", auth) - - // Ensure that the authentication data - // object is initiated at the beginning - // so it is present when serialized. - - auth.Data = make(map[string]interface{}) + c.Set(varKeyAuth, auth) // Start off with an authentication level // which prevents running any sql queries, @@ -77,7 +87,6 @@ func auth() fibre.MiddlewareFunc { subs := strings.Split(bits[0], "-") if len(subs) == 2 { - auth.Kind = cnf.AuthSC auth.Possible.NS = subs[0] auth.Selected.NS = subs[0] auth.Possible.DB = subs[1] @@ -88,8 +97,7 @@ func auth() fibre.MiddlewareFunc { // the request headers, then mark it as // the selected namespace. - if ns := c.Request().Header().Get("NS"); len(ns) != 0 { - auth.Kind = cnf.AuthSC + if ns := c.Request().Header().Get(varKeyNs); len(ns) != 0 { auth.Possible.NS = ns auth.Selected.NS = ns } @@ -98,8 +106,7 @@ func auth() fibre.MiddlewareFunc { // the request headers, then mark it as // the selected database. - if db := c.Request().Header().Get("DB"); len(db) != 0 { - auth.Kind = cnf.AuthSC + if db := c.Request().Header().Get(varKeyDb); len(db) != 0 { auth.Possible.DB = db auth.Selected.DB = db } @@ -117,7 +124,6 @@ func auth() fibre.MiddlewareFunc { if len(head) == 0 { for _, prot := range websocket.Subprotocols(c.Request().Request) { if len(prot) > 7 && prot[0:7] == "bearer-" { - head = "Bearer " + prot[7:] return checkBearer(c, prot[7:], func() error { return h(c) }) @@ -129,8 +135,8 @@ func auth() fibre.MiddlewareFunc { // is a Basic Auth header, and if it is then // process this as root authentication. - if len(head) > 0 && head[:5] == "Basic" { - return checkMaster(c, head[6:], func() error { + if len(head) > 6 && head[:5] == "Basic" { + return checkBasics(c, head[6:], func() error { return h(c) }) } @@ -139,7 +145,7 @@ func auth() fibre.MiddlewareFunc { // is a Bearer Auth header, and if it is then // 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 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) { - - 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) + auth := c.Get(varKeyAuth).(*cnf.Auth) user := []byte(cnf.Settings.Auth.User) 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.Possible.NS = "*" 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) { - auth := c.Get("auth").(*cnf.Auth) + auth := c.Get(varKeyAuth).(*cnf.Auth) var txn kvs.TX + var res []*db.Response var vars jwt.MapClaims - var nok, dok, sok, tok, uok bool - var nsv, dbv, scv, tkv, usv string + var nsk, dbk, sck, tkk, usk, tbk, idk bool + var nsv, dbv, scv, tkv, usv, tbv, idv string // Start a new read transaction. @@ -212,6 +252,10 @@ func checkBearer(c *fibre.Context, info string, callback func() error) (err erro defer txn.Cancel() + // Setup the kvs layer cache. + + cache := mem.NewWithTX(txn) + // Parse the specified JWT Token. 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 } - if val, ok := vars["auth"].(map[string]interface{}); ok { - auth.Data = val - } - - nsv, nok = vars["NS"].(string) // Namespace - dbv, dok = vars["DB"].(string) // Database - scv, sok = vars["SC"].(string) // Scope - tkv, tok = vars["TK"].(string) // Token - usv, uok = vars["US"].(string) // Login + nsv, nsk = vars[varKeyNs].(string) // Namespace + dbv, dbk = vars[varKeyDb].(string) // Database + scv, sck = vars[varKeySc].(string) // Scope + tkv, tkk = vars[varKeyTk].(string) // Token + usv, usk = vars[varKeyUs].(string) // Login + tbv, tbk = vars[varKeyTb].(string) // Table + idv, idk = vars[varKeyId].(string) // Thing if tkv == "default" { 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 { 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" { - key, err := mem.New(txn).GetST(nsv, dbv, scv, tkv) + key, err := cache.GetST(nsv, dbv, scv, tkv) if err != nil { 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 } - } else if nok && dok && tok { + } else if nsk && dbk && tkk { if tkv != "default" { - key, err := mem.New(txn).GetDT(nsv, dbv, tkv) + key, err := cache.GetDT(nsv, dbv, tkv) if err != nil { 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 return key.Code, nil - } else if uok { - usr, err := mem.New(txn).GetDU(nsv, dbv, usv) + } else if usk { + usr, err := cache.GetDU(nsv, dbv, usv) if err != nil { 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 } - } else if nok && tok { + } else if nsk && tkk { if tkv != "default" { - key, err := mem.New(txn).GetNT(nsv, tkv) + key, err := cache.GetNT(nsv, tkv) if err != nil { 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 return key.Code, nil - } else if uok { - usr, err := mem.New(txn).GetNU(nsv, usv) + } else if usk { + usr, err := cache.GetNU(nsv, usv) if err != nil { 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 } + return callback() + } - return callback() + return fibre.NewHTTPError(401).WithMessage("Invalid authentication details") } diff --git a/web/signin.go b/web/signin.go index aac9213e..e9544052 100644 --- a/web/signin.go +++ b/web/signin.go @@ -18,11 +18,12 @@ import ( "time" "github.com/abcum/fibre" + "github.com/abcum/surreal/cnf" "github.com/abcum/surreal/db" "github.com/abcum/surreal/kvs" "github.com/abcum/surreal/mem" "github.com/abcum/surreal/sql" - + "github.com/abcum/surreal/util/data" "github.com/dgrijalva/jwt-go" "golang.org/x/crypto/bcrypt" ) @@ -33,9 +34,21 @@ func signin(c *fibre.Context) (err error) { c.Bind(&vars) - n, nok := vars["NS"].(string) - d, dok := vars["DB"].(string) - s, sok := vars["SC"].(string) + n, nok := vars[varKeyNs].(string) + d, dok := vars[varKeyDb].(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 // 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 { + var ok bool var txn kvs.TX var str string + var doc *sql.Thing var res []*db.Response + var exp *sql.SubExpression var scp *sql.DefineScopeStatement // Start a new read transaction. @@ -60,7 +76,7 @@ func signin(c *fibre.Context) (err error) { // 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{}{ "ns": n, "db": d, @@ -68,11 +84,25 @@ func signin(c *fibre.Context) (err error) { }).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. - 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{}{ "ns": n, "db": d, @@ -80,7 +110,19 @@ func signin(c *fibre.Context) (err error) { }).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{}{ "ns": n, "db": d, @@ -91,15 +133,16 @@ func signin(c *fibre.Context) (err error) { // Create a new token signer with the default claims. signr := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{ - "NS": n, - "DB": d, - "SC": s, - "TK": "default", - "iss": "Surreal", - "iat": time.Now().Unix(), - "nbf": time.Now().Unix(), - "exp": time.Now().Add(scp.Time).Unix(), - "auth": res[0].Result[0], + "NS": n, + "DB": d, + "SC": s, + "TK": "default", + "iss": "Surreal", + "iat": time.Now().Unix(), + "nbf": time.Now().Unix(), + "exp": time.Now().Add(scp.Time).Unix(), + "TB": doc.TB, + "ID": doc.ID, }) // 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") } - 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 { - var txn kvs.TX var str string var usr *sql.DefineLoginStatement // Get the specified user and password. - u, uok := vars["user"].(string) - p, pok := vars["pass"].(string) + u, uok := vars[varKeyUser].(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{}{ "ns": n, "db": d, @@ -141,32 +183,8 @@ func signin(c *fibre.Context) (err error) { // Start a new read transaction. - if txn, err = db.Begin(false); err != nil { - return fibre.NewHTTPError(500) - } - - // 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") + if usr, err = signinDB(n, d, u, p); err != nil { + return err } // Create a new token signer with the default claims. @@ -189,10 +207,10 @@ func signin(c *fibre.Context) (err error) { "ns": n, "db": d, "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 { - var txn kvs.TX var str string var usr *sql.DefineLoginStatement // Get the specified user and password. - u, uok := vars["user"].(string) - p, pok := vars["pass"].(string) + u, uok := vars[varKeyUser].(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{}{ "ns": n, "nu": u, }).WithMessage("Database signin was unsuccessful") } - // Start a new read transaction. - - 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") + if usr, err = signinNS(n, u, p); err != nil { + return err } // 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{}{ "ns": n, "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) } + +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 + +} diff --git a/web/signup.go b/web/signup.go index 89651eac..6ea91d23 100644 --- a/web/signup.go +++ b/web/signup.go @@ -16,10 +16,12 @@ package web import ( "github.com/abcum/fibre" + "github.com/abcum/surreal/cnf" "github.com/abcum/surreal/db" "github.com/abcum/surreal/kvs" "github.com/abcum/surreal/mem" "github.com/abcum/surreal/sql" + "github.com/abcum/surreal/util/data" ) func signup(c *fibre.Context) (err error) { @@ -28,9 +30,21 @@ func signup(c *fibre.Context) (err error) { c.Bind(&vars) - n, nok := vars["NS"].(string) - d, dok := vars["DB"].(string) - s, sok := vars["SC"].(string) + n, nok := vars[varKeyNs].(string) + d, dok := vars[varKeyDb].(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 // 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 { + var ok bool var txn kvs.TX var res []*db.Response + var exp *sql.SubExpression var scp *sql.DefineScopeStatement // Start a new read transaction. @@ -54,7 +70,7 @@ func signup(c *fibre.Context) (err error) { // 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{}{ "ns": n, "db": d, @@ -62,19 +78,9 @@ func signup(c *fibre.Context) (err error) { }).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 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 { + if exp, ok = scp.Signup.(*sql.SubExpression); !ok { return fibre.NewHTTPError(403).WithFields(map[string]interface{}{ "ns": n, "db": d, @@ -82,10 +88,46 @@ func signup(c *fibre.Context) (err error) { }).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) }