Add ON SIGNUP and ON SIGNIN events to SQL SCOPE

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.
This commit is contained in:
Tobie Morgan Hitchcock 2018-03-18 21:30:02 +00:00
parent 0dae9d2366
commit ca392f87a7
7 changed files with 107 additions and 15 deletions

View file

@ -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;
)
;
`

View file

@ -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.

View file

@ -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
}

View file

@ -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

View file

@ -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)),
)
}

View file

@ -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
}

View file

@ -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
}