auth
auth/profile_postgres.go
Clean up sessions and tokens after Profile is deleted. Add a terminateSessionsByProfile method to our sessionStore to mark Sessions associated with a Profile as inactive. Implement memstore and postgres implementations of the terminateSessionsByProfile method. Add a TerminateSessionsByProfile wrapper method to Context. Add a revokeTokensByProfileID method to our tokenStore to mark Tokens associated with a Profile as revoked. Implement memstore and postgres implementation of the revokeTokensByProfileID method. Add a RevokeTokensByProfileID wrapper method to Context. Call our RevokeTokensByProfileID and TerminateSessionsByProfile methods after a Profile is deleted, to clean up the Tokens and Sessions associated with it.
| paddy@148 | 1 package auth |
| paddy@148 | 2 |
| paddy@148 | 3 import ( |
| paddy@148 | 4 "time" |
| paddy@148 | 5 |
| paddy@148 | 6 "code.secondbit.org/uuid.hg" |
| paddy@149 | 7 "github.com/lib/pq" |
| paddy@148 | 8 "github.com/secondbit/pan" |
| paddy@148 | 9 ) |
| paddy@148 | 10 |
| paddy@148 | 11 func (p Profile) GetSQLTableName() string { |
| paddy@148 | 12 return "profiles" |
| paddy@148 | 13 } |
| paddy@148 | 14 |
| paddy@148 | 15 func (l Login) GetSQLTableName() string { |
| paddy@148 | 16 return "logins" |
| paddy@148 | 17 } |
| paddy@148 | 18 |
| paddy@148 | 19 func (p *postgres) getProfileByIDSQL(id uuid.ID) *pan.Query { |
| paddy@148 | 20 var profile Profile |
| paddy@149 | 21 fields, _ := pan.GetFields(profile) |
| paddy@148 | 22 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(profile)) |
| paddy@148 | 23 query.IncludeWhere() |
| paddy@161 | 24 query.Include(pan.GetUnquotedColumn(profile, "ID")+" = ?", id) |
| paddy@148 | 25 return query.FlushExpressions(" ") |
| paddy@148 | 26 } |
| paddy@148 | 27 |
| paddy@148 | 28 func (p *postgres) getProfileByID(id uuid.ID) (Profile, error) { |
| paddy@148 | 29 query := p.getProfileByIDSQL(id) |
| paddy@148 | 30 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@148 | 31 if err != nil { |
| paddy@148 | 32 return Profile{}, err |
| paddy@148 | 33 } |
| paddy@148 | 34 var profile Profile |
| paddy@149 | 35 var found bool |
| paddy@148 | 36 for rows.Next() { |
| paddy@148 | 37 err := pan.Unmarshal(rows, &profile) |
| paddy@148 | 38 if err != nil { |
| paddy@148 | 39 return Profile{}, err |
| paddy@148 | 40 } |
| paddy@149 | 41 found = true |
| paddy@148 | 42 } |
| paddy@148 | 43 if err := rows.Err(); err != nil { |
| paddy@148 | 44 return Profile{}, err |
| paddy@148 | 45 } |
| paddy@149 | 46 if !found { |
| paddy@149 | 47 return profile, ErrProfileNotFound |
| paddy@149 | 48 } |
| paddy@148 | 49 return profile, nil |
| paddy@148 | 50 } |
| paddy@148 | 51 |
| paddy@148 | 52 func (p *postgres) getProfileByLoginSQL(value string) *pan.Query { |
| paddy@148 | 53 var profile Profile |
| paddy@148 | 54 var login Login |
| paddy@149 | 55 fields, _ := pan.GetUnquotedAbsoluteFields(profile) |
| paddy@148 | 56 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(profile)) |
| paddy@148 | 57 query.Include("INNER JOIN " + pan.GetTableName(login)) |
| paddy@149 | 58 query.Include("ON " + pan.GetUnquotedAbsoluteColumn(profile, "ID") + " = " + pan.GetUnquotedAbsoluteColumn(login, "ProfileID")) |
| paddy@148 | 59 query.IncludeWhere() |
| paddy@161 | 60 query.Include(pan.GetUnquotedAbsoluteColumn(login, "Value")+" = ?", value) |
| paddy@148 | 61 return query.FlushExpressions(" ") |
| paddy@148 | 62 } |
| paddy@148 | 63 |
| paddy@148 | 64 func (p *postgres) getProfileByLogin(value string) (Profile, error) { |
| paddy@148 | 65 query := p.getProfileByLoginSQL(value) |
| paddy@148 | 66 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@148 | 67 if err != nil { |
| paddy@148 | 68 return Profile{}, err |
| paddy@148 | 69 } |
| paddy@148 | 70 var profile Profile |
| paddy@149 | 71 var found bool |
| paddy@148 | 72 for rows.Next() { |
| paddy@148 | 73 err := pan.Unmarshal(rows, &profile) |
| paddy@148 | 74 if err != nil { |
| paddy@148 | 75 return Profile{}, err |
| paddy@148 | 76 } |
| paddy@149 | 77 found = true |
| paddy@148 | 78 } |
| paddy@148 | 79 if err := rows.Err(); err != nil { |
| paddy@148 | 80 return Profile{}, err |
| paddy@148 | 81 } |
| paddy@149 | 82 if !found { |
| paddy@149 | 83 return profile, ErrProfileNotFound |
| paddy@149 | 84 } |
| paddy@148 | 85 return profile, nil |
| paddy@148 | 86 } |
| paddy@148 | 87 |
| paddy@148 | 88 func (p *postgres) saveProfileSQL(profile Profile) *pan.Query { |
| paddy@149 | 89 fields, values := pan.GetFields(profile) |
| paddy@148 | 90 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(profile)) |
| paddy@148 | 91 query.Include("(" + pan.QueryList(fields) + ")") |
| paddy@148 | 92 query.Include("VALUES") |
| paddy@148 | 93 query.Include("("+pan.VariableList(len(values))+")", values...) |
| paddy@148 | 94 return query.FlushExpressions(" ") |
| paddy@148 | 95 } |
| paddy@148 | 96 |
| paddy@148 | 97 func (p *postgres) saveProfile(profile Profile) error { |
| paddy@148 | 98 query := p.saveProfileSQL(profile) |
| paddy@148 | 99 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@149 | 100 if e, ok := err.(*pq.Error); ok && e.Constraint == "profiles_pkey" { |
| paddy@149 | 101 err = ErrProfileAlreadyExists |
| paddy@149 | 102 } |
| paddy@148 | 103 return err |
| paddy@148 | 104 } |
| paddy@148 | 105 |
| paddy@148 | 106 func (p *postgres) updateProfileSQL(id uuid.ID, change ProfileChange) *pan.Query { |
| paddy@148 | 107 var profile Profile |
| paddy@148 | 108 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(profile)+" SET ") |
| paddy@149 | 109 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "Name")+" = ?", change.Name) |
| paddy@149 | 110 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "Passphrase")+" = ?", change.Passphrase) |
| paddy@149 | 111 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "Iterations")+" = ?", change.Iterations) |
| paddy@149 | 112 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "Salt")+" = ?", change.Salt) |
| paddy@149 | 113 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "PassphraseScheme")+" = ?", change.PassphraseScheme) |
| paddy@149 | 114 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "Compromised")+" = ?", change.Compromised) |
| paddy@149 | 115 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "LockedUntil")+" = ?", change.LockedUntil) |
| paddy@149 | 116 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "PassphraseReset")+" = ?", change.PassphraseReset) |
| paddy@149 | 117 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "PassphraseResetCreated")+" = ?", change.PassphraseResetCreated) |
| paddy@149 | 118 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "LastSeen")+" = ?", change.LastSeen) |
| paddy@148 | 119 query.FlushExpressions(", ") |
| paddy@148 | 120 query.IncludeWhere() |
| paddy@150 | 121 query.Include(pan.GetUnquotedColumn(profile, "ID")+" = ?", id) |
| paddy@148 | 122 return query.FlushExpressions(" ") |
| paddy@148 | 123 } |
| paddy@148 | 124 |
| paddy@148 | 125 func (p *postgres) updateProfile(id uuid.ID, change ProfileChange) error { |
| paddy@149 | 126 if change.Empty() { |
| paddy@149 | 127 return nil |
| paddy@149 | 128 } |
| paddy@148 | 129 query := p.updateProfileSQL(id, change) |
| paddy@148 | 130 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@148 | 131 return err |
| paddy@148 | 132 } |
| paddy@148 | 133 |
| paddy@148 | 134 func (p *postgres) updateProfilesSQL(ids []uuid.ID, change BulkProfileChange) *pan.Query { |
| paddy@149 | 135 if change.Empty() { |
| paddy@149 | 136 return nil |
| paddy@149 | 137 } |
| paddy@148 | 138 var profile Profile |
| paddy@148 | 139 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(profile)+" SET ") |
| paddy@149 | 140 query.IncludeIfNotNil(pan.GetUnquotedColumn(profile, "Compromised")+" = ?", change.Compromised) |
| paddy@148 | 141 query.FlushExpressions(", ") |
| paddy@148 | 142 query.IncludeWhere() |
| paddy@148 | 143 intids := make([]interface{}, len(ids)) |
| paddy@148 | 144 for pos, id := range ids { |
| paddy@148 | 145 intids[pos] = id |
| paddy@148 | 146 } |
| paddy@149 | 147 query.Include(pan.GetUnquotedColumn(profile, "ID")+" IN ("+pan.VariableList(len(ids))+")", intids...) |
| paddy@148 | 148 return query.FlushExpressions(" ") |
| paddy@148 | 149 } |
| paddy@148 | 150 |
| paddy@148 | 151 func (p *postgres) updateProfiles(ids []uuid.ID, change BulkProfileChange) error { |
| paddy@148 | 152 query := p.updateProfilesSQL(ids, change) |
| paddy@148 | 153 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@148 | 154 return err |
| paddy@148 | 155 } |
| paddy@148 | 156 |
| paddy@161 | 157 func (p *postgres) deleteProfileSQL(id uuid.ID) *pan.Query { |
| paddy@161 | 158 var profile Profile |
| paddy@161 | 159 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(profile)) |
| paddy@161 | 160 query.IncludeWhere() |
| paddy@161 | 161 query.Include(pan.GetUnquotedColumn(profile, "ID")+" = ?", id) |
| paddy@161 | 162 return query.FlushExpressions(" ") |
| paddy@161 | 163 } |
| paddy@161 | 164 |
| paddy@161 | 165 func (p *postgres) deleteProfile(id uuid.ID) error { |
| paddy@161 | 166 query := p.deleteProfileSQL(id) |
| paddy@161 | 167 res, err := p.db.Exec(query.String(), query.Args...) |
| paddy@161 | 168 if err != nil { |
| paddy@161 | 169 return err |
| paddy@161 | 170 } |
| paddy@161 | 171 rows, err := res.RowsAffected() |
| paddy@161 | 172 if err != nil { |
| paddy@161 | 173 return err |
| paddy@161 | 174 } |
| paddy@161 | 175 if rows == 0 { |
| paddy@161 | 176 return ErrProfileNotFound |
| paddy@161 | 177 } |
| paddy@161 | 178 return nil |
| paddy@161 | 179 } |
| paddy@161 | 180 |
| paddy@148 | 181 func (p *postgres) addLoginSQL(login Login) *pan.Query { |
| paddy@149 | 182 fields, values := pan.GetFields(login) |
| paddy@148 | 183 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(login)) |
| paddy@148 | 184 query.Include("(" + pan.QueryList(fields) + ")") |
| paddy@148 | 185 query.Include("VALUES") |
| paddy@148 | 186 query.Include("("+pan.VariableList(len(values))+")", values...) |
| paddy@148 | 187 return query.FlushExpressions(" ") |
| paddy@148 | 188 } |
| paddy@148 | 189 |
| paddy@148 | 190 func (p *postgres) addLogin(login Login) error { |
| paddy@148 | 191 query := p.addLoginSQL(login) |
| paddy@148 | 192 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@149 | 193 if e, ok := err.(*pq.Error); ok && e.Constraint == "logins_pkey" { |
| paddy@149 | 194 return ErrLoginAlreadyExists |
| paddy@149 | 195 } |
| paddy@148 | 196 return err |
| paddy@148 | 197 } |
| paddy@148 | 198 |
| paddy@160 | 199 func (p *postgres) removeLoginsByProfileSQL(profile uuid.ID) *pan.Query { |
| paddy@160 | 200 var login Login |
| paddy@160 | 201 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(login)) |
| paddy@160 | 202 query.IncludeWhere() |
| paddy@160 | 203 query.Include(pan.GetUnquotedColumn(login, "ProfileID")+" = ?", profile) |
| paddy@160 | 204 return query.FlushExpressions(" ") |
| paddy@160 | 205 } |
| paddy@160 | 206 |
| paddy@160 | 207 func (p *postgres) removeLoginsByProfile(profile uuid.ID) error { |
| paddy@160 | 208 query := p.removeLoginsByProfileSQL(profile) |
| paddy@160 | 209 res, err := p.db.Exec(query.String(), query.Args...) |
| paddy@160 | 210 if err != nil { |
| paddy@160 | 211 return err |
| paddy@160 | 212 } |
| paddy@160 | 213 rows, err := res.RowsAffected() |
| paddy@160 | 214 if err != nil { |
| paddy@160 | 215 return err |
| paddy@160 | 216 } |
| paddy@160 | 217 if rows == 0 { |
| paddy@160 | 218 return ErrProfileNotFound |
| paddy@160 | 219 } |
| paddy@160 | 220 return nil |
| paddy@160 | 221 } |
| paddy@160 | 222 |
| paddy@148 | 223 func (p *postgres) removeLoginSQL(value string, profile uuid.ID) *pan.Query { |
| paddy@148 | 224 var login Login |
| paddy@148 | 225 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(login)) |
| paddy@148 | 226 query.IncludeWhere() |
| paddy@150 | 227 query.Include(pan.GetUnquotedColumn(login, "Value")+" = ? AND "+pan.GetUnquotedColumn(login, "ProfileID")+" = ?", value, profile) |
| paddy@148 | 228 return query.FlushExpressions(" ") |
| paddy@148 | 229 } |
| paddy@148 | 230 |
| paddy@148 | 231 func (p *postgres) removeLogin(value string, profile uuid.ID) error { |
| paddy@148 | 232 query := p.removeLoginSQL(value, profile) |
| paddy@149 | 233 res, err := p.db.Exec(query.String(), query.Args...) |
| paddy@149 | 234 if err != nil { |
| paddy@149 | 235 return err |
| paddy@149 | 236 } |
| paddy@149 | 237 rows, err := res.RowsAffected() |
| paddy@149 | 238 if err != nil { |
| paddy@149 | 239 return err |
| paddy@149 | 240 } |
| paddy@149 | 241 if rows == 0 { |
| paddy@149 | 242 return ErrLoginNotFound |
| paddy@149 | 243 } |
| paddy@149 | 244 return nil |
| paddy@148 | 245 } |
| paddy@148 | 246 |
| paddy@148 | 247 func (p *postgres) recordLoginUseSQL(value string, when time.Time) *pan.Query { |
| paddy@148 | 248 var login Login |
| paddy@148 | 249 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(login)+" SET ") |
| paddy@150 | 250 query.Include(pan.GetUnquotedColumn(login, "LastUsed")+" = ?", when) |
| paddy@148 | 251 query.IncludeWhere() |
| paddy@150 | 252 query.Include(pan.GetUnquotedColumn(login, "Value")+" = ?", value) |
| paddy@148 | 253 return query.FlushExpressions(" ") |
| paddy@148 | 254 } |
| paddy@148 | 255 |
| paddy@148 | 256 func (p *postgres) recordLoginUse(value string, when time.Time) error { |
| paddy@148 | 257 query := p.recordLoginUseSQL(value, when) |
| paddy@148 | 258 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@148 | 259 return err |
| paddy@148 | 260 } |
| paddy@148 | 261 |
| paddy@148 | 262 func (p *postgres) listLoginsSQL(profile uuid.ID, num, offset int) *pan.Query { |
| paddy@148 | 263 var login Login |
| paddy@149 | 264 fields, _ := pan.GetFields(login) |
| paddy@148 | 265 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(login)) |
| paddy@148 | 266 query.IncludeWhere() |
| paddy@150 | 267 query.Include(pan.GetUnquotedColumn(login, "ProfileID")+" = ?", profile) |
| paddy@148 | 268 query.IncludeLimit(int64(num)) |
| paddy@148 | 269 query.IncludeOffset(int64(offset)) |
| paddy@148 | 270 return query.FlushExpressions(" ") |
| paddy@148 | 271 } |
| paddy@148 | 272 |
| paddy@148 | 273 func (p *postgres) listLogins(profile uuid.ID, num, offset int) ([]Login, error) { |
| paddy@148 | 274 query := p.listLoginsSQL(profile, num, offset) |
| paddy@148 | 275 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@148 | 276 if err != nil { |
| paddy@148 | 277 return []Login{}, err |
| paddy@148 | 278 } |
| paddy@148 | 279 var logins []Login |
| paddy@148 | 280 for rows.Next() { |
| paddy@148 | 281 var login Login |
| paddy@148 | 282 err = pan.Unmarshal(rows, &login) |
| paddy@148 | 283 if err != nil { |
| paddy@148 | 284 return logins, err |
| paddy@148 | 285 } |
| paddy@148 | 286 logins = append(logins, login) |
| paddy@148 | 287 } |
| paddy@148 | 288 if err := rows.Err(); err != nil { |
| paddy@148 | 289 return logins, err |
| paddy@148 | 290 } |
| paddy@148 | 291 return logins, nil |
| paddy@148 | 292 } |