auth

Paddy 2015-07-15 Parent:b0d1b3e39fc8 Child:4b68bac597b7

178:0a2c3d677161 Go to Latest

auth/client/client.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@172 1 package client
paddy@172 2
paddy@172 3 import (
paddy@172 4 "bytes"
paddy@172 5 "encoding/json"
paddy@172 6 "errors"
paddy@172 7 "io"
paddy@172 8 "net/http"
paddy@172 9 "strings"
paddy@173 10 "time"
paddy@172 11
paddy@172 12 "code.secondbit.org/auth.hg"
paddy@173 13 "code.secondbit.org/uuid.hg"
paddy@172 14 )
paddy@172 15
paddy@172 16 var (
paddy@172 17 ErrNilClient = errors.New("nil client wrapper")
paddy@172 18 ErrNilHTTPClient = errors.New("nil client")
paddy@172 19 )
paddy@172 20
paddy@172 21 type Client struct {
paddy@172 22 client *http.Client
paddy@172 23 address string
paddy@173 24 ID uuid.ID
paddy@172 25 }
paddy@172 26
paddy@173 27 func New(address string, id uuid.ID) *Client {
paddy@172 28 address = strings.TrimRight(address, "/")
paddy@172 29 return &Client{
paddy@172 30 address: address,
paddy@172 31 client: &http.Client{},
paddy@173 32 ID: id,
paddy@172 33 }
paddy@172 34 }
paddy@172 35
paddy@173 36 func (c *Client) do(method, url string, request interface{}, scopes []string, subject uuid.ID) (auth.Response, error) {
paddy@172 37 if c == nil {
paddy@172 38 return auth.Response{}, ErrNilClient
paddy@172 39 }
paddy@172 40 if c.client == nil {
paddy@172 41 return auth.Response{}, ErrNilHTTPClient
paddy@172 42 }
paddy@172 43 var response auth.Response
paddy@172 44 if !strings.HasPrefix(url, "http") {
paddy@172 45 url = strings.TrimLeft(url, "/")
paddy@172 46 url = "/" + url
paddy@172 47 url = c.address + url
paddy@172 48 }
paddy@172 49 var body io.Reader
paddy@172 50 if request != nil {
paddy@172 51 data, err := json.Marshal(request)
paddy@172 52 if err != nil {
paddy@172 53 return response, err
paddy@172 54 }
paddy@172 55 body = bytes.NewBuffer(data)
paddy@172 56 }
paddy@172 57 req, err := http.NewRequest(method, url, body)
paddy@172 58 if err != nil {
paddy@172 59 return response, err
paddy@172 60 }
paddy@173 61 req.Header.Set("Ducky-Scope", strings.Join(scopes, " "))
paddy@173 62 req.Header.Set("Ducky-Issuer", c.ID.String())
paddy@173 63 if subject != nil {
paddy@173 64 req.Header.Set("Ducky-Subject", subject.String())
paddy@173 65 }
paddy@173 66 req.Header.Set("Ducky-Expires", time.Now().Add(time.Hour).String())
paddy@173 67 req.Header.Set("Ducky-Issued-At", time.Now().String())
paddy@173 68 req.Header.Set("Ducky-Not-Before", time.Now().Add(-5*time.Minute).String())
paddy@172 69 resp, err := c.client.Do(req)
paddy@172 70 if err != nil {
paddy@172 71 return response, err
paddy@172 72 }
paddy@172 73 defer resp.Body.Close()
paddy@172 74 switch resp.Header.Get("Content-Type") {
paddy@172 75 case "application/json":
paddy@172 76 dec := json.NewDecoder(resp.Body)
paddy@172 77 err = dec.Decode(&response)
paddy@172 78 if err != nil {
paddy@172 79 return response, err
paddy@172 80 }
paddy@172 81 default:
paddy@172 82 dec := json.NewDecoder(resp.Body)
paddy@172 83 err = dec.Decode(&response)
paddy@172 84 if err != nil {
paddy@172 85 return response, err
paddy@172 86 }
paddy@172 87 }
paddy@172 88 return response, nil
paddy@172 89 }
paddy@172 90
paddy@173 91 func (c *Client) Get(url string, scopes []string, subject uuid.ID) (auth.Response, error) {
paddy@173 92 return c.do("GET", url, nil, scopes, subject)
paddy@172 93 }