auth

Paddy 2015-07-15 Parent:581c60f8dd23

178:0a2c3d677161 Go to Latest

auth/token_postgres.go

Update to use a generic event emitter. Rather can creating a purpose-built event emitter for each and every event we need to emit (I'm looking at you, login verification event) which is _downright silly_, we're now using a generic event publisher that's based on saying "HEY A MODEL UPDATED". This means we need to change all our setup code in authd to use events.NewNSQPublisher or events.NewStdoutPublisher instead of our homegrown solutions. Which also means updating our config to take an events.Publisher instead of our LoginVerificationNotifier (blergh). Our Context also now uses an events.Publisher instead of a LoginVerificationNotifier. Party all around! We also replaced our SendLoginVerification helper method on Context with a SendModelEvent helper method on Context, which is just a light wrapper around events.PublishModelEvent. Of course, all this means we need to update our email_verification listener to listen to the correct channel (based on the model we want updates about) and filter down to a Created action or our new custom action for "the customer wants their verification resent", which I'm OK making a special case and not generic, because c'mon. But we had a subtle change to all our constants, some of which are unofficial constants now. I'm unsure how I feel about this. We also updated our email_verification listener so that we're unmarshalling to a custom loginEvent, which is just an events.Event that overwrites the Data property to be an auth.Login instance. This is to make sure we don't need to wrangle a map[string]interface{}, which is no fun. I'm also OK with special-casing like this, because it's 1) a tiny amount of code, 2) properly utilising composition, and 3) the only way I can think of to cleanly accomplish what I want. I also added a note about GetLogin's deficient handling of logins, namely that it doesn't recognise admins and return Verification codes to them, which would be a useful property for internal tools to take advantage of. Ah well. I updated the Profile and Login implementations so they're now event.Model instances, mainly by just exporting some strings from them through getters that will let us automatically build an Event from them. This lets us use the PublishModelEvent helper. I updated our CreateProfileHandler to properly mangle the login Verification property, and to fire off the ActionCreated events for the new Login and the new Profile. I updated our GetLoginHandler and UpdateLoginHandler to properly mangle the loginVerification property. God that's annoying. :-/ You'll note I didn't start publishing the events.ActionUpdated or events.ActionDeleted events for Profiles or Logins yet, and didn't bother publishing any events for literally any other type. That's because I'm a lazy piece of crap and will end up publishing them when I absolutely have to. Part of that is because if a channel isn't created/being read for a topic, the messages will just stack up in NSQ, and I don't want that. But mostly I'm lazy. Finally, I got to delete the entire profile_verification.go file, because we're no longer special-casing that. Hooray!

History
paddy@155 1 package auth
paddy@155 2
paddy@155 3 import (
paddy@155 4 "code.secondbit.org/uuid.hg"
paddy@155 5
paddy@155 6 "github.com/lib/pq"
paddy@155 7 "github.com/secondbit/pan"
paddy@155 8 )
paddy@155 9
paddy@155 10 func (t Token) GetSQLTableName() string {
paddy@155 11 return "tokens"
paddy@155 12 }
paddy@155 13
paddy@155 14 func (p *postgres) getTokenSQL(token string, refresh bool) *pan.Query {
paddy@155 15 var t Token
paddy@155 16 fields, _ := pan.GetFields(t)
paddy@155 17 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(t))
paddy@155 18 query.IncludeWhere()
paddy@155 19 if !refresh {
paddy@155 20 query.Include(pan.GetUnquotedColumn(t, "AccessToken")+" = ?", token)
paddy@155 21 } else {
paddy@155 22 query.Include(pan.GetUnquotedColumn(t, "RefreshToken")+" = ?", token)
paddy@155 23 }
paddy@155 24 return query.FlushExpressions(" ")
paddy@155 25 }
paddy@155 26
paddy@155 27 func (p *postgres) getToken(token string, refresh bool) (Token, error) {
paddy@155 28 query := p.getTokenSQL(token, refresh)
paddy@155 29 rows, err := p.db.Query(query.String(), query.Args...)
paddy@155 30 if err != nil {
paddy@155 31 return Token{}, err
paddy@155 32 }
paddy@155 33 var t Token
paddy@155 34 var found bool
paddy@155 35 for rows.Next() {
paddy@155 36 err := pan.Unmarshal(rows, &t)
paddy@155 37 if err != nil {
paddy@155 38 return t, err
paddy@155 39 }
paddy@155 40 found = true
paddy@155 41 }
paddy@155 42 if err = rows.Err(); err != nil {
paddy@155 43 return t, err
paddy@155 44 }
paddy@155 45 if !found {
paddy@155 46 return t, ErrTokenNotFound
paddy@155 47 }
paddy@155 48 return t, nil
paddy@155 49 }
paddy@155 50
paddy@155 51 func (p *postgres) saveTokenSQL(token Token) *pan.Query {
paddy@155 52 fields, values := pan.GetFields(token)
paddy@155 53 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(token))
paddy@155 54 query.Include("(" + pan.QueryList(fields) + ")")
paddy@155 55 query.Include("VALUES")
paddy@155 56 query.Include("("+pan.VariableList(len(values))+")", values...)
paddy@155 57 return query.FlushExpressions(" ")
paddy@155 58 }
paddy@155 59
paddy@155 60 func (p *postgres) saveToken(token Token) error {
paddy@155 61 query := p.saveTokenSQL(token)
paddy@155 62 _, err := p.db.Exec(query.String(), query.Args...)
paddy@155 63 if e, ok := err.(*pq.Error); ok && e.Constraint == "tokens_pkey" {
paddy@155 64 err = ErrTokenAlreadyExists
paddy@155 65 }
paddy@155 66 if err != nil || len(token.Scopes) < 1 {
paddy@155 67 return err
paddy@155 68 }
paddy@155 69 return err
paddy@155 70 }
paddy@155 71
paddy@168 72 func (p *postgres) revokeTokenSQL(token string) *pan.Query {
paddy@155 73 var t Token
paddy@155 74 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
paddy@155 75 query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
paddy@155 76 query.IncludeWhere()
paddy@168 77 query.Include(pan.GetUnquotedColumn(t, "RefreshToken")+" = ?", token)
paddy@155 78 return query.FlushExpressions(" ")
paddy@155 79 }
paddy@155 80
paddy@168 81 func (p *postgres) revokeToken(token string) error {
paddy@168 82 query := p.revokeTokenSQL(token)
paddy@155 83 res, err := p.db.Exec(query.String(), query.Args...)
paddy@155 84 if err != nil {
paddy@155 85 return err
paddy@155 86 }
paddy@155 87 rows, err := res.RowsAffected()
paddy@155 88 if err != nil {
paddy@155 89 return err
paddy@155 90 }
paddy@155 91 if rows == 0 {
paddy@155 92 return ErrTokenNotFound
paddy@155 93 }
paddy@155 94 return nil
paddy@155 95 }
paddy@155 96
paddy@162 97 func (p *postgres) revokeTokensByProfileIDSQL(profileID uuid.ID) *pan.Query {
paddy@162 98 var t Token
paddy@162 99 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
paddy@162 100 query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
paddy@162 101 query.IncludeWhere()
paddy@162 102 query.Include(pan.GetUnquotedColumn(t, "ProfileID")+" = ?", profileID)
paddy@162 103 return query.FlushExpressions(" ")
paddy@162 104 }
paddy@162 105
paddy@162 106 func (p *postgres) revokeTokensByProfileID(profileID uuid.ID) error {
paddy@162 107 query := p.revokeTokensByProfileIDSQL(profileID)
paddy@162 108 res, err := p.db.Exec(query.String(), query.Args...)
paddy@162 109 if err != nil {
paddy@162 110 return err
paddy@162 111 }
paddy@162 112 rows, err := res.RowsAffected()
paddy@162 113 if err != nil {
paddy@162 114 return err
paddy@162 115 }
paddy@162 116 if rows == 0 {
paddy@162 117 return ErrProfileNotFound
paddy@162 118 }
paddy@162 119 return nil
paddy@162 120 }
paddy@162 121
paddy@164 122 func (p *postgres) revokeTokensByClientIDSQL(clientID uuid.ID) *pan.Query {
paddy@164 123 var t Token
paddy@164 124 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(t)+" SET ")
paddy@164 125 query.Include(pan.GetUnquotedColumn(t, "Revoked")+" = ?", true)
paddy@164 126 query.IncludeWhere()
paddy@164 127 query.Include(pan.GetUnquotedColumn(t, "ClientID")+" = ?", clientID)
paddy@164 128 return query.FlushExpressions(" ")
paddy@164 129 }
paddy@164 130
paddy@164 131 func (p *postgres) revokeTokensByClientID(clientID uuid.ID) error {
paddy@164 132 query := p.revokeTokensByClientIDSQL(clientID)
paddy@164 133 res, err := p.db.Exec(query.String(), query.Args...)
paddy@164 134 if err != nil {
paddy@164 135 return err
paddy@164 136 }
paddy@164 137 rows, err := res.RowsAffected()
paddy@164 138 if err != nil {
paddy@164 139 return err
paddy@164 140 }
paddy@164 141 if rows == 0 {
paddy@164 142 return ErrClientNotFound
paddy@164 143 }
paddy@164 144 return nil
paddy@164 145 }
paddy@164 146
paddy@155 147 func (p *postgres) getTokensByProfileIDSQL(profileID uuid.ID, num, offset int) *pan.Query {
paddy@155 148 var token Token
paddy@155 149 fields, _ := pan.GetFields(token)
paddy@155 150 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(token))
paddy@155 151 query.IncludeWhere()
paddy@155 152 query.Include(pan.GetUnquotedColumn(token, "ProfileID")+" = ?", profileID)
paddy@155 153 query.IncludeLimit(int64(num))
paddy@155 154 query.IncludeOffset(int64(offset))
paddy@155 155 return query.FlushExpressions(" ")
paddy@155 156 }
paddy@155 157
paddy@155 158 func (p *postgres) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
paddy@155 159 query := p.getTokensByProfileIDSQL(profileID, num, offset)
paddy@155 160 rows, err := p.db.Query(query.String(), query.Args...)
paddy@155 161 if err != nil {
paddy@155 162 return []Token{}, err
paddy@155 163 }
paddy@155 164 var tokens []Token
paddy@155 165 var tokenIDs []string
paddy@155 166 for rows.Next() {
paddy@155 167 var token Token
paddy@155 168 err = pan.Unmarshal(rows, &token)
paddy@155 169 if err != nil {
paddy@155 170 return tokens, err
paddy@155 171 }
paddy@155 172 tokens = append(tokens, token)
paddy@155 173 tokenIDs = append(tokenIDs, token.AccessToken)
paddy@155 174 }
paddy@155 175 if err = rows.Err(); err != nil {
paddy@155 176 return tokens, err
paddy@155 177 }
paddy@155 178 return tokens, nil
paddy@155 179 }