auth
auth/client_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.
| paddy@151 | 1 package auth |
| paddy@151 | 2 |
| paddy@151 | 3 import ( |
| paddy@151 | 4 "code.secondbit.org/uuid.hg" |
| paddy@151 | 5 "github.com/lib/pq" |
| paddy@151 | 6 "github.com/secondbit/pan" |
| paddy@151 | 7 ) |
| paddy@151 | 8 |
| paddy@151 | 9 func (c Client) GetSQLTableName() string { |
| paddy@151 | 10 return "clients" |
| paddy@151 | 11 } |
| paddy@151 | 12 |
| paddy@151 | 13 func (e Endpoint) GetSQLTableName() string { |
| paddy@151 | 14 return "endpoints" |
| paddy@151 | 15 } |
| paddy@151 | 16 |
| paddy@151 | 17 func (p *postgres) getClientSQL(id uuid.ID) *pan.Query { |
| paddy@151 | 18 var client Client |
| paddy@151 | 19 fields, _ := pan.GetFields(client) |
| paddy@151 | 20 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(client)) |
| paddy@151 | 21 query.IncludeWhere() |
| paddy@151 | 22 query.Include(pan.GetUnquotedColumn(client, "ID")+" = ? AND "+pan.GetUnquotedColumn(client, "Deleted")+" = ?", id, false) |
| paddy@151 | 23 return query.FlushExpressions(" ") |
| paddy@151 | 24 } |
| paddy@151 | 25 |
| paddy@151 | 26 func (p *postgres) getClient(id uuid.ID) (Client, error) { |
| paddy@151 | 27 query := p.getClientSQL(id) |
| paddy@151 | 28 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@151 | 29 if err != nil { |
| paddy@151 | 30 return Client{}, err |
| paddy@151 | 31 } |
| paddy@151 | 32 var client Client |
| paddy@151 | 33 var found bool |
| paddy@151 | 34 for rows.Next() { |
| paddy@151 | 35 err := pan.Unmarshal(rows, &client) |
| paddy@151 | 36 if err != nil { |
| paddy@151 | 37 return client, err |
| paddy@151 | 38 } |
| paddy@151 | 39 found = true |
| paddy@151 | 40 } |
| paddy@151 | 41 if err = rows.Err(); err != nil { |
| paddy@151 | 42 return client, err |
| paddy@151 | 43 } |
| paddy@151 | 44 if !found { |
| paddy@151 | 45 return client, ErrClientNotFound |
| paddy@151 | 46 } |
| paddy@151 | 47 return client, nil |
| paddy@151 | 48 } |
| paddy@151 | 49 |
| paddy@151 | 50 func (p *postgres) saveClientSQL(client Client) *pan.Query { |
| paddy@151 | 51 fields, values := pan.GetFields(client) |
| paddy@151 | 52 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(client)) |
| paddy@151 | 53 query.Include("(" + pan.QueryList(fields) + ")") |
| paddy@151 | 54 query.Include("VALUES") |
| paddy@151 | 55 query.Include("("+pan.VariableList(len(values))+")", values...) |
| paddy@151 | 56 return query.FlushExpressions(" ") |
| paddy@151 | 57 } |
| paddy@151 | 58 |
| paddy@151 | 59 func (p *postgres) saveClient(client Client) error { |
| paddy@151 | 60 query := p.saveClientSQL(client) |
| paddy@151 | 61 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@151 | 62 if e, ok := err.(*pq.Error); ok && e.Constraint == "clients_pkey" { |
| paddy@151 | 63 err = ErrClientAlreadyExists |
| paddy@151 | 64 } |
| paddy@151 | 65 return err |
| paddy@151 | 66 } |
| paddy@151 | 67 |
| paddy@151 | 68 func (p *postgres) updateClientSQL(id uuid.ID, change ClientChange) *pan.Query { |
| paddy@151 | 69 var client Client |
| paddy@151 | 70 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(client)+" SET ") |
| paddy@151 | 71 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Secret")+" = ?", change.Secret) |
| paddy@151 | 72 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "OwnerID")+" = ?", change.OwnerID) |
| paddy@151 | 73 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Name")+" = ?", change.Name) |
| paddy@151 | 74 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Logo")+" = ?", change.Logo) |
| paddy@151 | 75 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Website")+" = ?", change.Website) |
| paddy@151 | 76 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Deleted")+" = ?", change.Deleted) |
| paddy@151 | 77 query.FlushExpressions(", ") |
| paddy@151 | 78 query.IncludeWhere() |
| paddy@151 | 79 query.Include(pan.GetUnquotedColumn(client, "ID")+" = ?", id) |
| paddy@151 | 80 return query.FlushExpressions(" ") |
| paddy@151 | 81 } |
| paddy@151 | 82 |
| paddy@151 | 83 func (p *postgres) updateClient(id uuid.ID, change ClientChange) error { |
| paddy@151 | 84 if change.Empty() { |
| paddy@151 | 85 return nil |
| paddy@151 | 86 } |
| paddy@151 | 87 query := p.updateClientSQL(id, change) |
| paddy@151 | 88 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@151 | 89 return err |
| paddy@151 | 90 } |
| paddy@151 | 91 |
| paddy@151 | 92 func (p *postgres) listClientsByOwnerSQL(ownerID uuid.ID, num, offset int) *pan.Query { |
| paddy@151 | 93 var client Client |
| paddy@151 | 94 fields, _ := pan.GetFields(client) |
| paddy@151 | 95 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(client)) |
| paddy@151 | 96 query.IncludeWhere() |
| paddy@151 | 97 query.Include(pan.GetUnquotedColumn(client, "OwnerID")+" = ? AND "+pan.GetUnquotedColumn(client, "Deleted")+" = ?", ownerID, false) |
| paddy@164 | 98 if num > 0 { |
| paddy@164 | 99 query.IncludeLimit(int64(num)) |
| paddy@164 | 100 } |
| paddy@151 | 101 query.IncludeOffset(int64(offset)) |
| paddy@151 | 102 return query.FlushExpressions(" ") |
| paddy@151 | 103 } |
| paddy@151 | 104 |
| paddy@151 | 105 func (p *postgres) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) { |
| paddy@151 | 106 query := p.listClientsByOwnerSQL(ownerID, num, offset) |
| paddy@151 | 107 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@151 | 108 if err != nil { |
| paddy@151 | 109 return []Client{}, err |
| paddy@151 | 110 } |
| paddy@151 | 111 var clients []Client |
| paddy@151 | 112 for rows.Next() { |
| paddy@151 | 113 var client Client |
| paddy@151 | 114 err = pan.Unmarshal(rows, &client) |
| paddy@151 | 115 if err != nil { |
| paddy@151 | 116 return clients, err |
| paddy@151 | 117 } |
| paddy@151 | 118 clients = append(clients, client) |
| paddy@151 | 119 } |
| paddy@151 | 120 if err = rows.Err(); err != nil { |
| paddy@151 | 121 return clients, err |
| paddy@151 | 122 } |
| paddy@151 | 123 return clients, nil |
| paddy@151 | 124 } |
| paddy@151 | 125 |
| paddy@164 | 126 func (p *postgres) deleteClientsByOwnerSQL(ownerID uuid.ID) *pan.Query { |
| paddy@164 | 127 var client Client |
| paddy@164 | 128 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(client)+" SET") |
| paddy@164 | 129 query.Include(pan.GetUnquotedColumn(client, "Deleted")+"= ?", true) |
| paddy@164 | 130 query.IncludeWhere() |
| paddy@164 | 131 query.Include(pan.GetUnquotedColumn(client, "OwnerID")+" = ?", ownerID) |
| paddy@164 | 132 return query.FlushExpressions(" ") |
| paddy@164 | 133 } |
| paddy@164 | 134 |
| paddy@164 | 135 func (p *postgres) deleteClientsByOwner(ownerID uuid.ID) error { |
| paddy@164 | 136 query := p.deleteClientsByOwnerSQL(ownerID) |
| paddy@164 | 137 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@164 | 138 return err |
| paddy@164 | 139 } |
| paddy@164 | 140 |
| paddy@151 | 141 func (p *postgres) addEndpointsSQL(endpoints []Endpoint) *pan.Query { |
| paddy@151 | 142 fields, _ := pan.GetFields(endpoints[0]) |
| paddy@151 | 143 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(endpoints[0])) |
| paddy@151 | 144 query.Include("(" + pan.QueryList(fields) + ")") |
| paddy@151 | 145 query.Include("VALUES") |
| paddy@151 | 146 query.FlushExpressions(" ") |
| paddy@151 | 147 for _, endpoint := range endpoints { |
| paddy@151 | 148 _, values := pan.GetFields(endpoint) |
| paddy@151 | 149 query.Include("("+pan.VariableList(len(values))+")", values...) |
| paddy@151 | 150 } |
| paddy@151 | 151 return query.FlushExpressions(", ") |
| paddy@151 | 152 } |
| paddy@151 | 153 |
| paddy@151 | 154 func (p *postgres) addEndpoints(endpoints []Endpoint) error { |
| paddy@151 | 155 if len(endpoints) < 1 { |
| paddy@151 | 156 return nil |
| paddy@151 | 157 } |
| paddy@151 | 158 query := p.addEndpointsSQL(endpoints) |
| paddy@151 | 159 _, err := p.db.Exec(query.String(), query.Args...) |
| paddy@151 | 160 if e, ok := err.(*pq.Error); ok && e.Constraint == "endpoints_pkey" { |
| paddy@151 | 161 return ErrEndpointAlreadyExists |
| paddy@151 | 162 } |
| paddy@151 | 163 return err |
| paddy@151 | 164 } |
| paddy@151 | 165 |
| paddy@151 | 166 func (p *postgres) removeEndpointSQL(client, endpoint uuid.ID) *pan.Query { |
| paddy@151 | 167 var e Endpoint |
| paddy@151 | 168 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(e)) |
| paddy@151 | 169 query.IncludeWhere() |
| paddy@151 | 170 query.Include(pan.GetUnquotedColumn(e, "ID")+" = ? AND "+pan.GetUnquotedColumn(e, "ClientID")+" = ?", endpoint, client) |
| paddy@151 | 171 return query.FlushExpressions(" ") |
| paddy@151 | 172 } |
| paddy@151 | 173 |
| paddy@151 | 174 func (p *postgres) removeEndpoint(client, endpoint uuid.ID) error { |
| paddy@151 | 175 query := p.removeEndpointSQL(client, endpoint) |
| paddy@151 | 176 res, err := p.db.Exec(query.String(), query.Args...) |
| paddy@151 | 177 if err != nil { |
| paddy@151 | 178 return err |
| paddy@151 | 179 } |
| paddy@151 | 180 rows, err := res.RowsAffected() |
| paddy@151 | 181 if err != nil { |
| paddy@151 | 182 return err |
| paddy@151 | 183 } |
| paddy@151 | 184 if rows == 0 { |
| paddy@151 | 185 return ErrEndpointNotFound |
| paddy@151 | 186 } |
| paddy@151 | 187 return nil |
| paddy@151 | 188 } |
| paddy@151 | 189 |
| paddy@164 | 190 func (p *postgres) removeEndpointsByClientIDSQL(client uuid.ID) *pan.Query { |
| paddy@164 | 191 var e Endpoint |
| paddy@164 | 192 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(e)) |
| paddy@164 | 193 query.IncludeWhere() |
| paddy@164 | 194 query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client) |
| paddy@164 | 195 return query.FlushExpressions(" ") |
| paddy@164 | 196 } |
| paddy@164 | 197 |
| paddy@164 | 198 func (p *postgres) removeEndpointsByClientID(client uuid.ID) error { |
| paddy@164 | 199 query := p.removeEndpointsByClientIDSQL(client) |
| paddy@164 | 200 res, err := p.db.Exec(query.String(), query.Args...) |
| paddy@164 | 201 if err != nil { |
| paddy@164 | 202 return err |
| paddy@164 | 203 } |
| paddy@164 | 204 rows, err := res.RowsAffected() |
| paddy@164 | 205 if err != nil { |
| paddy@164 | 206 return err |
| paddy@164 | 207 } |
| paddy@164 | 208 if rows == 0 { |
| paddy@164 | 209 return ErrClientNotFound |
| paddy@164 | 210 } |
| paddy@164 | 211 return nil |
| paddy@164 | 212 } |
| paddy@164 | 213 |
| paddy@151 | 214 func (p *postgres) getEndpointSQL(client, endpoint uuid.ID) *pan.Query { |
| paddy@151 | 215 var e Endpoint |
| paddy@151 | 216 fields, _ := pan.GetFields(e) |
| paddy@151 | 217 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(e)) |
| paddy@151 | 218 query.IncludeWhere() |
| paddy@151 | 219 query.FlushExpressions(" ") |
| paddy@151 | 220 query.Include(pan.GetUnquotedColumn(e, "ID")+" = ?", endpoint) |
| paddy@151 | 221 query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client) |
| paddy@151 | 222 return query.FlushExpressions(" AND ") |
| paddy@151 | 223 } |
| paddy@151 | 224 |
| paddy@151 | 225 func (p *postgres) getEndpoint(client, endpoint uuid.ID) (Endpoint, error) { |
| paddy@151 | 226 query := p.getEndpointSQL(client, endpoint) |
| paddy@151 | 227 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@151 | 228 if err != nil { |
| paddy@151 | 229 return Endpoint{}, err |
| paddy@151 | 230 } |
| paddy@151 | 231 var e Endpoint |
| paddy@151 | 232 var found bool |
| paddy@151 | 233 for rows.Next() { |
| paddy@151 | 234 err := pan.Unmarshal(rows, &e) |
| paddy@151 | 235 if err != nil { |
| paddy@151 | 236 return e, err |
| paddy@151 | 237 } |
| paddy@151 | 238 found = true |
| paddy@151 | 239 } |
| paddy@151 | 240 if err = rows.Err(); err != nil { |
| paddy@151 | 241 return e, err |
| paddy@151 | 242 } |
| paddy@151 | 243 if !found { |
| paddy@151 | 244 return e, ErrEndpointNotFound |
| paddy@151 | 245 } |
| paddy@151 | 246 return e, nil |
| paddy@151 | 247 } |
| paddy@151 | 248 |
| paddy@151 | 249 func (p *postgres) checkEndpointSQL(client uuid.ID, endpoint string) *pan.Query { |
| paddy@151 | 250 var e Endpoint |
| paddy@151 | 251 fields, _ := pan.GetFields(e) |
| paddy@151 | 252 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(e)) |
| paddy@151 | 253 query.IncludeWhere() |
| paddy@151 | 254 query.FlushExpressions(" ") |
| paddy@151 | 255 query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client) |
| paddy@151 | 256 query.Include(pan.GetUnquotedColumn(e, "NormalizedURI")+" = ?", endpoint) |
| paddy@151 | 257 return query.FlushExpressions(" AND ") |
| paddy@151 | 258 } |
| paddy@151 | 259 |
| paddy@151 | 260 func (p *postgres) checkEndpoint(client uuid.ID, endpoint string) (bool, error) { |
| paddy@151 | 261 query := p.checkEndpointSQL(client, endpoint) |
| paddy@151 | 262 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@151 | 263 if err != nil { |
| paddy@151 | 264 return false, err |
| paddy@151 | 265 } |
| paddy@151 | 266 var found bool |
| paddy@151 | 267 for rows.Next() { |
| paddy@151 | 268 found = true |
| paddy@151 | 269 } |
| paddy@151 | 270 if err = rows.Err(); err != nil { |
| paddy@151 | 271 return found, err |
| paddy@151 | 272 } |
| paddy@151 | 273 return found, nil |
| paddy@151 | 274 } |
| paddy@151 | 275 |
| paddy@151 | 276 func (p *postgres) listEndpointsSQL(client uuid.ID, num, offset int) *pan.Query { |
| paddy@151 | 277 var endpoint Endpoint |
| paddy@151 | 278 fields, _ := pan.GetFields(endpoint) |
| paddy@151 | 279 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(endpoint)) |
| paddy@151 | 280 query.IncludeWhere() |
| paddy@151 | 281 query.Include(pan.GetUnquotedColumn(endpoint, "ClientID")+" = ?", client) |
| paddy@151 | 282 query.IncludeLimit(int64(num)) |
| paddy@151 | 283 query.IncludeOffset(int64(offset)) |
| paddy@151 | 284 return query.FlushExpressions(" ") |
| paddy@151 | 285 } |
| paddy@151 | 286 |
| paddy@151 | 287 func (p *postgres) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) { |
| paddy@151 | 288 query := p.listEndpointsSQL(client, num, offset) |
| paddy@151 | 289 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@151 | 290 if err != nil { |
| paddy@151 | 291 return []Endpoint{}, err |
| paddy@151 | 292 } |
| paddy@151 | 293 var endpoints []Endpoint |
| paddy@151 | 294 for rows.Next() { |
| paddy@151 | 295 var endpoint Endpoint |
| paddy@151 | 296 err = pan.Unmarshal(rows, &endpoint) |
| paddy@151 | 297 if err != nil { |
| paddy@151 | 298 return endpoints, err |
| paddy@151 | 299 } |
| paddy@151 | 300 endpoints = append(endpoints, endpoint) |
| paddy@151 | 301 } |
| paddy@151 | 302 if err = rows.Err(); err != nil { |
| paddy@151 | 303 return endpoints, err |
| paddy@151 | 304 } |
| paddy@151 | 305 return endpoints, nil |
| paddy@151 | 306 } |
| paddy@151 | 307 |
| paddy@151 | 308 func (p *postgres) countEndpointsSQL(client uuid.ID) *pan.Query { |
| paddy@151 | 309 var endpoint Endpoint |
| paddy@151 | 310 query := pan.New(pan.POSTGRES, "SELECT COUNT(*) FROM "+pan.GetTableName(endpoint)) |
| paddy@151 | 311 query.IncludeWhere() |
| paddy@151 | 312 query.Include(pan.GetUnquotedColumn(endpoint, "ClientID")+" = ?", client) |
| paddy@151 | 313 return query.FlushExpressions(" ") |
| paddy@151 | 314 } |
| paddy@151 | 315 |
| paddy@151 | 316 func (p *postgres) countEndpoints(client uuid.ID) (int64, error) { |
| paddy@151 | 317 query := p.countEndpointsSQL(client) |
| paddy@151 | 318 rows, err := p.db.Query(query.String(), query.Args...) |
| paddy@151 | 319 if err != nil { |
| paddy@151 | 320 return 0, err |
| paddy@151 | 321 } |
| paddy@151 | 322 var results int64 |
| paddy@151 | 323 for rows.Next() { |
| paddy@151 | 324 err = pan.Unmarshal(rows, &results) |
| paddy@151 | 325 if err != nil { |
| paddy@151 | 326 return results, err |
| paddy@151 | 327 } |
| paddy@151 | 328 } |
| paddy@151 | 329 if err = rows.Err(); err != nil { |
| paddy@151 | 330 return results, err |
| paddy@151 | 331 } |
| paddy@151 | 332 return results, nil |
| paddy@151 | 333 } |