auth

Paddy 2015-05-15 Parent:849f3820b164 Child:8ecb60d29b0d

168:581c60f8dd23 Go to Latest

auth/profile_postgres.go

Switch to a JWT approach. We're going to use a JWT as our access tokens (as discussed in &yet's excellent post https://blog.andyet.com/2015/05/12/micro-services-user-info-and-auth and my ensuing conversation with Fritzy). The benefit of this approach is that we can do authentication and even some authorization without touching the database at all. The drawback is that we can no longer revoke access tokens, only the refresh tokens that grant the access tokens. We need a new config variable to set our private key, used to sign the JWT. We get to remove our token handlers, as we no longer can revoke tokens, so there's no purpose in getting information about it or listing them. Our tokenStore revokeToken gets to be simplified, as it will only ever be used for refresh tokens now. We also updated our postgres and memstore implementations. We added a helper method for generating the signed "access token" (our JWT) and started using it in the places where we're creating a Token. We get to remove the `revoked` SQL column for the tokens table, and rename the `refresh_revoked` column to just be `revoked`. We shortened our access token expiration to 15 minutes instead of an hour, to deal with the token not being revokable.

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