From ca392f87a724f1631611f94a5d544c24aa68b60e Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Sun, 18 Mar 2018 21:30:02 +0000 Subject: [PATCH] Add ON SIGNUP and ON SIGNIN events to SQL SCOPE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is now possible to run queries when a user signs-up or signs-in, by specifying multiple queries within an ON SIGNUP (…) clause, or a ON SIGNIN (...) clause. --- db/define_test.go | 8 ++++++++ sql/ast.go | 20 +++++++++++--------- sql/cork.go | 4 ++++ sql/scope.go | 22 +++++++++++++++++++++- sql/string.go | 4 +++- web/signin.go | 32 ++++++++++++++++++++++++++++++-- web/signup.go | 32 ++++++++++++++++++++++++++++++-- 7 files changed, 107 insertions(+), 15 deletions(-) diff --git a/db/define_test.go b/db/define_test.go index 87163219..fb5598d2 100644 --- a/db/define_test.go +++ b/db/define_test.go @@ -71,6 +71,14 @@ func TestDefine(t *testing.T) { SIGNIN AS ( SELECT * FROM user WHERE email=$user AND bcrypt.compare(pass, $pass) ) + ON SIGNUP ( + UPDATE $id SET times.created=time.now(); + CREATE activity SET kind="signup", user=$id; + ) + ON SIGNIN ( + UPDATE $id SET times.login=time.now(); + CREATE activity SET kind="signin", user=$id; + ) ; ` diff --git a/sql/ast.go b/sql/ast.go index f79ec7df..646918df 100644 --- a/sql/ast.go +++ b/sql/ast.go @@ -335,15 +335,17 @@ type RemoveTokenStatement struct { // DefineScopeStatement represents an SQL DEFINE SCOPE statement. type DefineScopeStatement struct { - KV string `cork:"-" codec:"-"` - NS string `cork:"-" codec:"-"` - DB string `cork:"-" codec:"-"` - Name *Ident `cork:"name" codec:"name"` - Time time.Duration `cork:"time" codec:"time"` - Code []byte `cork:"code" codec:"code"` - Signup Expr `cork:"signup" codec:"signup"` - Signin Expr `cork:"signin" codec:"signin"` - Connect Expr `cork:"connect" codec:"connect"` + KV string `cork:"-" codec:"-"` + NS string `cork:"-" codec:"-"` + DB string `cork:"-" codec:"-"` + Name *Ident `cork:"name" codec:"name"` + Time time.Duration `cork:"time" codec:"time"` + Code []byte `cork:"code" codec:"code"` + Signup Expr `cork:"signup" codec:"signup"` + Signin Expr `cork:"signin" codec:"signin"` + Connect Expr `cork:"connect" codec:"connect"` + OnSignup Expr `cork:"onsignup" codec:"onsignup"` + OnSignin Expr `cork:"onsignin" codec:"onsignin"` } // RemoveScopeStatement represents an SQL REMOVE SCOPE statement. diff --git a/sql/cork.go b/sql/cork.go index 41e99bdc..4227f6f3 100644 --- a/sql/cork.go +++ b/sql/cork.go @@ -1569,6 +1569,8 @@ func (this *DefineScopeStatement) MarshalCORK(w *cork.Writer) (err error) { w.EncodeAny(this.Signup) w.EncodeAny(this.Signin) w.EncodeAny(this.Connect) + w.EncodeAny(this.OnSignup) + w.EncodeAny(this.OnSignin) return } @@ -1579,6 +1581,8 @@ func (this *DefineScopeStatement) UnmarshalCORK(r *cork.Reader) (err error) { r.DecodeAny(&this.Signup) r.DecodeAny(&this.Signin) r.DecodeAny(&this.Connect) + r.DecodeAny(&this.OnSignup) + r.DecodeAny(&this.OnSignin) return } diff --git a/sql/scope.go b/sql/scope.go index b6c71ad6..56397fc4 100644 --- a/sql/scope.go +++ b/sql/scope.go @@ -28,7 +28,7 @@ func (p *parser) parseDefineScopeStatement() (stmt *DefineScopeStatement, err er for { - tok, _, exi := p.mightBe(SESSION, SIGNUP, SIGNIN, CONNECT) + tok, _, exi := p.mightBe(SESSION, SIGNUP, SIGNIN, CONNECT, ON) if !exi { break } @@ -60,6 +60,26 @@ func (p *parser) parseDefineScopeStatement() (stmt *DefineScopeStatement, err er } } + if is(tok, ON) { + + tok, _, err = p.shouldBe(SIGNIN, SIGNUP) + if err != nil { + return nil, err + } + + switch tok { + case SIGNUP: + if stmt.OnSignup, err = p.parseMult(); err != nil { + return nil, err + } + case SIGNIN: + if stmt.OnSignin, err = p.parseMult(); err != nil { + return nil, err + } + } + + } + } return diff --git a/sql/string.go b/sql/string.go index 09de1df6..d14f0399 100644 --- a/sql/string.go +++ b/sql/string.go @@ -334,12 +334,14 @@ func (this RemoveTokenStatement) String() string { } func (this DefineScopeStatement) String() string { - return print("DEFINE SCOPE %v%v%v%v%v", + return print("DEFINE SCOPE %v%v%v%v%v%v%v", this.Name, maybe(this.Time > 0, print(" SESSION %v", this.Time)), maybe(this.Signup != nil, print(" SIGNUP AS %v", this.Signup)), maybe(this.Signin != nil, print(" SIGNIN AS %v", this.Signin)), maybe(this.Connect != nil, print(" CONNECT AS %v", this.Connect)), + maybe(this.OnSignup != nil, print(" ON SIGNUP %v", this.OnSignup)), + maybe(this.OnSignin != nil, print(" ON SIGNIN %v", this.OnSignin)), ) } diff --git a/web/signin.go b/web/signin.go index 742f4a66..c1722712 100644 --- a/web/signin.go +++ b/web/signin.go @@ -92,6 +92,7 @@ func signinInternal(c *fibre.Context, vars map[string]interface{}) (str string, var doc *sql.Thing var res []*db.Response var exp *sql.SubExpression + var evt *sql.MultExpression var scp *sql.DefineScopeStatement // Start a new read transaction. @@ -104,6 +105,10 @@ func signinInternal(c *fibre.Context, vars map[string]interface{}) (str string, defer txn.Cancel() + // Give full permissions to scope. + + c.Set(varKeyAuth, &cnf.Auth{Kind: cnf.AuthDB}) + // Specify fields to show in logs. f := map[string]interface{}{"ns": n, "db": d, "sc": s} @@ -124,8 +129,6 @@ func signinInternal(c *fibre.Context, vars map[string]interface{}) (str string, // Process the scope signin 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. @@ -186,6 +189,31 @@ func signinInternal(c *fibre.Context, vars map[string]interface{}) (str string, return str, fibre.NewHTTPError(403).WithFields(f).WithMessage(m) } + // Check that the scope allows signup. + + if evt, ok = scp.OnSignin.(*sql.MultExpression); ok { + + stmts := make([]sql.Statement, len(evt.Expr)) + + for k := range evt.Expr { + stmts[k] = evt.Expr[k] + } + + query := &sql.Query{Statements: stmts} + + qvars := map[string]interface{}{ + "id": doc, + } + + // If the query fails then return a 501 error. + + if res, err = db.Process(c, query, qvars); err != nil { + m := "Authentication scope signin was unsuccessful: `ON SIGNIN` failed:" + err.Error() + return str, fibre.NewHTTPError(501).WithFields(f).WithMessage(m) + } + + } + return str, err } diff --git a/web/signup.go b/web/signup.go index 508c808a..e05e521b 100644 --- a/web/signup.go +++ b/web/signup.go @@ -91,6 +91,7 @@ func signupInternal(c *fibre.Context, vars map[string]interface{}) (str string, var doc *sql.Thing var res []*db.Response var exp *sql.SubExpression + var evt *sql.MultExpression var scp *sql.DefineScopeStatement // Start a new read transaction. @@ -103,6 +104,10 @@ func signupInternal(c *fibre.Context, vars map[string]interface{}) (str string, defer txn.Cancel() + // Give full permissions to scope. + + c.Set(varKeyAuth, &cnf.Auth{Kind: cnf.AuthDB}) + // Specify fields to show in logs. f := map[string]interface{}{"ns": n, "db": d, "sc": s} @@ -123,8 +128,6 @@ func signupInternal(c *fibre.Context, vars map[string]interface{}) (str string, // 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. @@ -185,6 +188,31 @@ func signupInternal(c *fibre.Context, vars map[string]interface{}) (str string, return str, fibre.NewHTTPError(403).WithFields(f).WithMessage(m) } + // Check that the scope allows signup. + + if evt, ok = scp.OnSignup.(*sql.MultExpression); ok { + + stmts := make([]sql.Statement, len(evt.Expr)) + + for k := range evt.Expr { + stmts[k] = evt.Expr[k] + } + + query := &sql.Query{Statements: stmts} + + qvars := map[string]interface{}{ + "id": doc, + } + + // If the query fails then return a 501 error. + + if res, err = db.Process(c, query, qvars); err != nil { + m := "Authentication scope signup was unsuccessful: `ON SIGNUP` failed:" + err.Error() + return str, fibre.NewHTTPError(501).WithFields(f).WithMessage(m) + } + + } + return str, err }