auth

Paddy 2014-10-26 Parent:a5987795707e Child:b3cd7765a7c8

57:e45bfa2abc00 Browse Files

The great documentation and exported interface cleanup. Modify all our *Store interfaces to be unexported, as there's no real good reason they need to be exported, especially as they can be implemented without being exported. The interfaces shouldn't matter to 99% of users of the package, so let's not pollute our package API. Further, all methods of the interfaces are now unexported, for pretty much the same reasoning. Add a doc.go file with documentation explaining the choices the package is making and what it provides. Implement documentation on all our exported types and methods and functions, which makes golint happy. The only remaining golint warning is about NewMemstore, which will stay the way it is. The memstore type is useful outside tests for things like standing up a server quickly when we don't care about the storage, and because the type is unexported, we _need_ a New function to create an instance that can be passed to the Context.

client.go client_test.go context.go doc.go grant.go grant_test.go http.go memstore.go profile.go profile_test.go token.go token_test.go

     1.1 --- a/client.go	Wed Oct 22 00:30:28 2014 -0400
     1.2 +++ b/client.go	Sun Oct 26 00:53:36 2014 -0400
     1.3 @@ -10,17 +10,28 @@
     1.4  )
     1.5  
     1.6  var (
     1.7 -	ErrNoClientStore       = errors.New("no ClientStore was specified for the Context")
     1.8 -	ErrClientNotFound      = errors.New("client not found in ClientStore")
     1.9 -	ErrClientAlreadyExists = errors.New("client already exists in ClientStore")
    1.10 +	// ErrNoClientStore is returned when a Context tries to act on a clientStore without setting one first.
    1.11 +	ErrNoClientStore = errors.New("no clientStore was specified for the Context")
    1.12 +	// ErrClientNotFound is returned when a Client is requested but not found in a clientStore.
    1.13 +	ErrClientNotFound = errors.New("client not found in clientStore")
    1.14 +	// ErrClientAlreadyExists is returned when a Client is added to a clientStore, but another Client with
    1.15 +	// the same ID already exists in the clientStore.
    1.16 +	ErrClientAlreadyExists = errors.New("client already exists in clientStore")
    1.17  
    1.18 -	ErrEmptyChange          = errors.New("change must have at least one change in it")
    1.19 -	ErrClientNameTooShort   = errors.New("client name must be at least 2 characters")
    1.20 -	ErrClientNameTooLong    = errors.New("client name must be at most 32 characters")
    1.21 -	ErrClientLogoTooLong    = errors.New("client logo must be at most 1024 characters")
    1.22 -	ErrClientLogoNotURL     = errors.New("client logo must be a valid absolute URL")
    1.23 +	// ErrEmptyChange is returned when a Change has all its properties set to nil.
    1.24 +	ErrEmptyChange = errors.New("change must have at least one property set")
    1.25 +	// ErrClientNameTooShort is returned when a Client's Name property is too short.
    1.26 +	ErrClientNameTooShort = errors.New("client name must be at least 2 characters")
    1.27 +	// ErrClientNameTooLong is returned when a Client's Name property is too long.
    1.28 +	ErrClientNameTooLong = errors.New("client name must be at most 32 characters")
    1.29 +	// ErrClientLogoTooLong is returned when a Client's Logo property is too long.
    1.30 +	ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters")
    1.31 +	// ErrClientLogoNotURL is returned when a Client's Logo property is not a valid absolute URL.
    1.32 +	ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL")
    1.33 +	// ErrClientWebsiteTooLong is returned when a Client's Website property is too long.
    1.34  	ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
    1.35 -	ErrClientWebsiteNotURL  = errors.New("client website must be a valid absolute URL")
    1.36 +	// ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
    1.37 +	ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
    1.38  )
    1.39  
    1.40  // Client represents a client that grants access
    1.41 @@ -36,6 +47,8 @@
    1.42  	Type    string
    1.43  }
    1.44  
    1.45 +// ApplyChange applies the properties of the passed
    1.46 +// ClientChange to the Client object it is called on.
    1.47  func (c *Client) ApplyChange(change ClientChange) {
    1.48  	if change.Secret != nil {
    1.49  		c.Secret = *change.Secret
    1.50 @@ -54,6 +67,8 @@
    1.51  	}
    1.52  }
    1.53  
    1.54 +// ClientChange represents a bundle of options for
    1.55 +// updating a Client's mutable data.
    1.56  type ClientChange struct {
    1.57  	Secret  *string
    1.58  	OwnerID uuid.ID
    1.59 @@ -62,6 +77,8 @@
    1.60  	Website *string
    1.61  }
    1.62  
    1.63 +// Validate checks the ClientChange it is called on
    1.64 +// and asserts its internal validity, or lack thereof.
    1.65  func (c ClientChange) Validate() error {
    1.66  	if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil {
    1.67  		return ErrEmptyChange
    1.68 @@ -93,6 +110,10 @@
    1.69  	return nil
    1.70  }
    1.71  
    1.72 +// Endpoint represents a single URI that a Client
    1.73 +// controls. Users will be redirected to these URIs
    1.74 +// following successful authorization grants and
    1.75 +// exchanges for access tokens.
    1.76  type Endpoint struct {
    1.77  	ID       uuid.ID
    1.78  	ClientID uuid.ID
    1.79 @@ -114,23 +135,21 @@
    1.80  	s[i], s[j] = s[j], s[i]
    1.81  }
    1.82  
    1.83 -// ClientStore abstracts the storage interface for
    1.84 -// storing and retrieving Clients.
    1.85 -type ClientStore interface {
    1.86 -	GetClient(id uuid.ID) (Client, error)
    1.87 -	SaveClient(client Client) error
    1.88 -	UpdateClient(id uuid.ID, change ClientChange) error
    1.89 -	DeleteClient(id uuid.ID) error
    1.90 -	ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
    1.91 +type clientStore interface {
    1.92 +	getClient(id uuid.ID) (Client, error)
    1.93 +	saveClient(client Client) error
    1.94 +	updateClient(id uuid.ID, change ClientChange) error
    1.95 +	deleteClient(id uuid.ID) error
    1.96 +	listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
    1.97  
    1.98 -	AddEndpoint(client uuid.ID, endpoint Endpoint) error
    1.99 -	RemoveEndpoint(client, endpoint uuid.ID) error
   1.100 -	CheckEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error)
   1.101 -	ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
   1.102 -	CountEndpoints(client uuid.ID) (int64, error)
   1.103 +	addEndpoint(client uuid.ID, endpoint Endpoint) error
   1.104 +	removeEndpoint(client, endpoint uuid.ID) error
   1.105 +	checkEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error)
   1.106 +	listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
   1.107 +	countEndpoints(client uuid.ID) (int64, error)
   1.108  }
   1.109  
   1.110 -func (m *Memstore) GetClient(id uuid.ID) (Client, error) {
   1.111 +func (m *memstore) getClient(id uuid.ID) (Client, error) {
   1.112  	m.clientLock.RLock()
   1.113  	defer m.clientLock.RUnlock()
   1.114  	c, ok := m.clients[id.String()]
   1.115 @@ -140,7 +159,7 @@
   1.116  	return c, nil
   1.117  }
   1.118  
   1.119 -func (m *Memstore) SaveClient(client Client) error {
   1.120 +func (m *memstore) saveClient(client Client) error {
   1.121  	m.clientLock.Lock()
   1.122  	defer m.clientLock.Unlock()
   1.123  	if _, ok := m.clients[client.ID.String()]; ok {
   1.124 @@ -151,7 +170,7 @@
   1.125  	return nil
   1.126  }
   1.127  
   1.128 -func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error {
   1.129 +func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
   1.130  	m.clientLock.Lock()
   1.131  	defer m.clientLock.Unlock()
   1.132  	c, ok := m.clients[id.String()]
   1.133 @@ -163,8 +182,8 @@
   1.134  	return nil
   1.135  }
   1.136  
   1.137 -func (m *Memstore) DeleteClient(id uuid.ID) error {
   1.138 -	client, err := m.GetClient(id)
   1.139 +func (m *memstore) deleteClient(id uuid.ID) error {
   1.140 +	client, err := m.getClient(id)
   1.141  	if err != nil {
   1.142  		return err
   1.143  	}
   1.144 @@ -184,7 +203,7 @@
   1.145  	return nil
   1.146  }
   1.147  
   1.148 -func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
   1.149 +func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
   1.150  	ids := m.lookupClientsByProfileID(ownerID.String())
   1.151  	if len(ids) > num+offset {
   1.152  		ids = ids[offset : num+offset]
   1.153 @@ -195,7 +214,7 @@
   1.154  	}
   1.155  	clients := []Client{}
   1.156  	for _, id := range ids {
   1.157 -		client, err := m.GetClient(id)
   1.158 +		client, err := m.getClient(id)
   1.159  		if err != nil {
   1.160  			return []Client{}, err
   1.161  		}
   1.162 @@ -204,14 +223,14 @@
   1.163  	return clients, nil
   1.164  }
   1.165  
   1.166 -func (m *Memstore) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
   1.167 +func (m *memstore) addEndpoint(client uuid.ID, endpoint Endpoint) error {
   1.168  	m.endpointLock.Lock()
   1.169  	defer m.endpointLock.Unlock()
   1.170  	m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoint)
   1.171  	return nil
   1.172  }
   1.173  
   1.174 -func (m *Memstore) RemoveEndpoint(client, endpoint uuid.ID) error {
   1.175 +func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
   1.176  	m.endpointLock.Lock()
   1.177  	defer m.endpointLock.Unlock()
   1.178  	pos := -1
   1.179 @@ -227,7 +246,7 @@
   1.180  	return nil
   1.181  }
   1.182  
   1.183 -func (m *Memstore) CheckEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error) {
   1.184 +func (m *memstore) checkEndpoint(client uuid.ID, endpoint string, strict bool) (bool, error) {
   1.185  	m.endpointLock.RLock()
   1.186  	defer m.endpointLock.RUnlock()
   1.187  	for _, candidate := range m.endpoints[client.String()] {
   1.188 @@ -240,13 +259,13 @@
   1.189  	return false, nil
   1.190  }
   1.191  
   1.192 -func (m *Memstore) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
   1.193 +func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
   1.194  	m.endpointLock.RLock()
   1.195  	defer m.endpointLock.RUnlock()
   1.196  	return m.endpoints[client.String()], nil
   1.197  }
   1.198  
   1.199 -func (m *Memstore) CountEndpoints(client uuid.ID) (int64, error) {
   1.200 +func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
   1.201  	m.endpointLock.RLock()
   1.202  	defer m.endpointLock.RUnlock()
   1.203  	return int64(len(m.endpoints[client.String()])), nil
     2.1 --- a/client_test.go	Wed Oct 22 00:30:28 2014 -0400
     2.2 +++ b/client_test.go	Sun Oct 26 00:53:36 2014 -0400
     2.3 @@ -18,7 +18,7 @@
     2.4  	clientChangeWebsite
     2.5  )
     2.6  
     2.7 -var clientStores = []ClientStore{NewMemstore()}
     2.8 +var clientStores = []clientStore{NewMemstore()}
     2.9  
    2.10  func compareClients(client1, client2 Client) (success bool, field string, val1, val2 interface{}) {
    2.11  	if !client1.ID.Equal(client2.ID) {
    2.12 @@ -72,15 +72,15 @@
    2.13  		Website: "website",
    2.14  	}
    2.15  	for _, store := range clientStores {
    2.16 -		err := store.SaveClient(client)
    2.17 +		err := store.saveClient(client)
    2.18  		if err != nil {
    2.19  			t.Fatalf("Error saving client to %T: %s", store, err)
    2.20  		}
    2.21 -		err = store.SaveClient(client)
    2.22 +		err = store.saveClient(client)
    2.23  		if err != ErrClientAlreadyExists {
    2.24  			t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store)
    2.25  		}
    2.26 -		retrieved, err := store.GetClient(client.ID)
    2.27 +		retrieved, err := store.getClient(client.ID)
    2.28  		if err != nil {
    2.29  			t.Fatalf("Error retrieving client from %T: %s", store, err)
    2.30  		}
    2.31 @@ -88,7 +88,7 @@
    2.32  		if !success {
    2.33  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
    2.34  		}
    2.35 -		clients, err := store.ListClientsByOwner(client.OwnerID, 25, 0)
    2.36 +		clients, err := store.listClientsByOwner(client.OwnerID, 25, 0)
    2.37  		if err != nil {
    2.38  			t.Fatalf("Error retrieving clients by owner from %T: %s", store, err)
    2.39  		}
    2.40 @@ -99,19 +99,19 @@
    2.41  		if !success {
    2.42  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
    2.43  		}
    2.44 -		err = store.DeleteClient(client.ID)
    2.45 +		err = store.deleteClient(client.ID)
    2.46  		if err != nil {
    2.47  			t.Fatalf("Error deleting client from %T: %s", store, err)
    2.48  		}
    2.49 -		err = store.DeleteClient(client.ID)
    2.50 +		err = store.deleteClient(client.ID)
    2.51  		if err != ErrClientNotFound {
    2.52  			t.Fatalf("Expected ErrClientNotFound, got %s from %T", err, store)
    2.53  		}
    2.54 -		retrieved, err = store.GetClient(client.ID)
    2.55 +		retrieved, err = store.getClient(client.ID)
    2.56  		if err != ErrClientNotFound {
    2.57  			t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err)
    2.58  		}
    2.59 -		clients, err = store.ListClientsByOwner(client.OwnerID, 25, 0)
    2.60 +		clients, err = store.listClientsByOwner(client.OwnerID, 25, 0)
    2.61  		if err != nil {
    2.62  			t.Fatalf("Error listing clients by owner from %T: %s", store, err)
    2.63  		}
    2.64 @@ -146,15 +146,15 @@
    2.65  		URI:      *uri2,
    2.66  	}
    2.67  	for _, store := range clientStores {
    2.68 -		err := store.SaveClient(client)
    2.69 +		err := store.saveClient(client)
    2.70  		if err != nil {
    2.71  			t.Fatalf("Error saving client to %T: %s", store, err)
    2.72  		}
    2.73 -		err = store.AddEndpoint(client.ID, endpoint1)
    2.74 +		err = store.addEndpoint(client.ID, endpoint1)
    2.75  		if err != nil {
    2.76  			t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
    2.77  		}
    2.78 -		endpoints, err := store.ListEndpoints(client.ID, 10, 0)
    2.79 +		endpoints, err := store.listEndpoints(client.ID, 10, 0)
    2.80  		if err != nil {
    2.81  			t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
    2.82  		}
    2.83 @@ -165,11 +165,11 @@
    2.84  		if !success {
    2.85  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
    2.86  		}
    2.87 -		err = store.AddEndpoint(client.ID, endpoint2)
    2.88 +		err = store.addEndpoint(client.ID, endpoint2)
    2.89  		if err != nil {
    2.90  			t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
    2.91  		}
    2.92 -		endpoints, err = store.ListEndpoints(client.ID, 10, 0)
    2.93 +		endpoints, err = store.listEndpoints(client.ID, 10, 0)
    2.94  		if err != nil {
    2.95  			t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
    2.96  		}
    2.97 @@ -187,11 +187,11 @@
    2.98  		if !success {
    2.99  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
   2.100  		}
   2.101 -		err = store.RemoveEndpoint(client.ID, endpoint1.ID)
   2.102 +		err = store.removeEndpoint(client.ID, endpoint1.ID)
   2.103  		if err != nil {
   2.104  			t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
   2.105  		}
   2.106 -		endpoints, err = store.ListEndpoints(client.ID, 10, 0)
   2.107 +		endpoints, err = store.listEndpoints(client.ID, 10, 0)
   2.108  		if err != nil {
   2.109  			t.Fatalf("Error listing endpoints in %T: %s", store, err)
   2.110  		}
   2.111 @@ -202,11 +202,11 @@
   2.112  		if !success {
   2.113  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
   2.114  		}
   2.115 -		err = store.RemoveEndpoint(client.ID, endpoint2.ID)
   2.116 +		err = store.removeEndpoint(client.ID, endpoint2.ID)
   2.117  		if err != nil {
   2.118  			t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
   2.119  		}
   2.120 -		endpoints, err = store.ListEndpoints(client.ID, 10, 0)
   2.121 +		endpoints, err = store.listEndpoints(client.ID, 10, 0)
   2.122  		if err != nil {
   2.123  			t.Fatalf("Error listing endpoints in %T: %s", store, err)
   2.124  		}
   2.125 @@ -262,15 +262,15 @@
   2.126  			t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
   2.127  		}
   2.128  		for _, store := range clientStores {
   2.129 -			err := store.SaveClient(client)
   2.130 +			err := store.saveClient(client)
   2.131  			if err != nil {
   2.132  				t.Fatalf("Error saving client in %T: %s", store, err)
   2.133  			}
   2.134 -			err = store.UpdateClient(client.ID, change)
   2.135 +			err = store.updateClient(client.ID, change)
   2.136  			if err != nil {
   2.137  				t.Fatalf("Error updating client in %T: %s", store, err)
   2.138  			}
   2.139 -			retrieved, err := store.GetClient(client.ID)
   2.140 +			retrieved, err := store.getClient(client.ID)
   2.141  			if err != nil {
   2.142  				t.Fatalf("Error getting profile from %T: %s", store, err)
   2.143  			}
   2.144 @@ -278,11 +278,11 @@
   2.145  			if !match {
   2.146  				t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
   2.147  			}
   2.148 -			err = store.DeleteClient(client.ID)
   2.149 +			err = store.deleteClient(client.ID)
   2.150  			if err != nil {
   2.151  				t.Fatalf("Error deleting client from %T: %s", store, err)
   2.152  			}
   2.153 -			err = store.UpdateClient(client.ID, change)
   2.154 +			err = store.updateClient(client.ID, change)
   2.155  			if err != ErrClientNotFound {
   2.156  				t.Fatalf("Expected ErrClientNotFound, got %v from %T", err, store)
   2.157  			}
   2.158 @@ -322,20 +322,20 @@
   2.159  		"https://www.example.com/my/full/path":     true,
   2.160  	}
   2.161  	for _, store := range clientStores {
   2.162 -		err := store.SaveClient(client)
   2.163 +		err := store.saveClient(client)
   2.164  		if err != nil {
   2.165  			t.Fatalf("Error saving client in %T: %s", store, err)
   2.166  		}
   2.167 -		err = store.AddEndpoint(client.ID, endpoint1)
   2.168 +		err = store.addEndpoint(client.ID, endpoint1)
   2.169  		if err != nil {
   2.170  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   2.171  		}
   2.172 -		err = store.AddEndpoint(client.ID, endpoint2)
   2.173 +		err = store.addEndpoint(client.ID, endpoint2)
   2.174  		if err != nil {
   2.175  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   2.176  		}
   2.177  		for candidate, expectation := range candidates {
   2.178 -			result, err := store.CheckEndpoint(client.ID, candidate, false)
   2.179 +			result, err := store.checkEndpoint(client.ID, candidate, false)
   2.180  			if err != nil {
   2.181  				t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
   2.182  			}
   2.183 @@ -384,20 +384,20 @@
   2.184  		"https://www.example.com/my/full/path":     true,
   2.185  	}
   2.186  	for _, store := range clientStores {
   2.187 -		err := store.SaveClient(client)
   2.188 +		err := store.saveClient(client)
   2.189  		if err != nil {
   2.190  			t.Fatalf("Error saving client in %T: %s", store, err)
   2.191  		}
   2.192 -		err = store.AddEndpoint(client.ID, endpoint1)
   2.193 +		err = store.addEndpoint(client.ID, endpoint1)
   2.194  		if err != nil {
   2.195  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   2.196  		}
   2.197 -		err = store.AddEndpoint(client.ID, endpoint2)
   2.198 +		err = store.addEndpoint(client.ID, endpoint2)
   2.199  		if err != nil {
   2.200  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   2.201  		}
   2.202  		for candidate, expectation := range candidates {
   2.203 -			result, err := store.CheckEndpoint(client.ID, candidate, true)
   2.204 +			result, err := store.checkEndpoint(client.ID, candidate, true)
   2.205  			if err != nil {
   2.206  				t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
   2.207  			}
     3.1 --- a/context.go	Wed Oct 22 00:30:28 2014 -0400
     3.2 +++ b/context.go	Sun Oct 26 00:53:36 2014 -0400
     3.3 @@ -1,7 +1,6 @@
     3.4  package auth
     3.5  
     3.6  import (
     3.7 -	"errors"
     3.8  	"html/template"
     3.9  	"io"
    3.10  	"log"
    3.11 @@ -10,21 +9,23 @@
    3.12  	"code.secondbit.org/uuid"
    3.13  )
    3.14  
    3.15 -var (
    3.16 -	ErrNoTemplate = errors.New("no Template specified for the Context")
    3.17 -)
    3.18 -
    3.19 +// Context wraps the different storage interfaces and should
    3.20 +// be used as the main point of interaction for the data storage
    3.21 +// layer.
    3.22  type Context struct {
    3.23  	template *template.Template
    3.24 -	clients  ClientStore
    3.25 -	grants   GrantStore
    3.26 -	profiles ProfileStore
    3.27 -	tokens   TokenStore
    3.28 +	clients  clientStore
    3.29 +	grants   grantStore
    3.30 +	profiles profileStore
    3.31 +	tokens   tokenStore
    3.32  }
    3.33  
    3.34 +// Render uses the HTML templates associated with the Context to render the
    3.35 +// template specified by name to out using data to fill any template variables.
    3.36  func (c Context) Render(out io.Writer, name string, data interface{}) {
    3.37  	if c.template == nil {
    3.38 -		log.Println(ErrNoTemplate)
    3.39 +		log.Println("No template set on Context, can't render anything!")
    3.40 +		return
    3.41  	}
    3.42  	err := c.template.ExecuteTemplate(out, name, data)
    3.43  	if err != nil {
    3.44 @@ -32,191 +33,248 @@
    3.45  	}
    3.46  }
    3.47  
    3.48 +// GetClient returns a single Client by its ID from the
    3.49 +// clientStore associated with the Context.
    3.50  func (c Context) GetClient(id uuid.ID) (Client, error) {
    3.51  	if c.clients == nil {
    3.52  		return Client{}, ErrNoClientStore
    3.53  	}
    3.54 -	return c.clients.GetClient(id)
    3.55 +	return c.clients.getClient(id)
    3.56  }
    3.57  
    3.58 +// SaveClient stores the passed Client in the clientStore
    3.59 +// associated with the Context.
    3.60  func (c Context) SaveClient(client Client) error {
    3.61  	if c.clients == nil {
    3.62  		return ErrNoClientStore
    3.63  	}
    3.64 -	return c.clients.SaveClient(client)
    3.65 +	return c.clients.saveClient(client)
    3.66  }
    3.67  
    3.68 +// UpdateClient applies the specified ClientChange to the Client
    3.69 +// with the specified ID in the clientStore associated with the
    3.70 +// Context.
    3.71  func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
    3.72  	if c.clients == nil {
    3.73  		return ErrNoClientStore
    3.74  	}
    3.75 -	return c.clients.UpdateClient(id, change)
    3.76 +	return c.clients.updateClient(id, change)
    3.77  }
    3.78  
    3.79 +// DeleteClient removes the client with the specified ID from the
    3.80 +// clientStore associated with the Context.
    3.81  func (c Context) DeleteClient(id uuid.ID) error {
    3.82  	if c.clients == nil {
    3.83  		return ErrNoClientStore
    3.84  	}
    3.85 -	return c.clients.DeleteClient(id)
    3.86 +	return c.clients.deleteClient(id)
    3.87  }
    3.88  
    3.89 +// ListClientsByOwner returns a slice of up to num Clients, starting at offset (inclusive)
    3.90 +// that have the specified OwnerID in the clientStore associated with the Context.
    3.91  func (c Context) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
    3.92  	if c.clients == nil {
    3.93  		return []Client{}, ErrNoClientStore
    3.94  	}
    3.95 -	return c.clients.ListClientsByOwner(ownerID, num, offset)
    3.96 +	return c.clients.listClientsByOwner(ownerID, num, offset)
    3.97  }
    3.98  
    3.99 +// AddEndpoint stores the specified Endpoint in the clientStore associated with the Context,
   3.100 +// and associates the newly-stored Endpoint with the Client specified by the passed ID.
   3.101  func (c Context) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
   3.102  	if c.clients == nil {
   3.103  		return ErrNoClientStore
   3.104  	}
   3.105 -	return c.clients.AddEndpoint(client, endpoint)
   3.106 +	return c.clients.addEndpoint(client, endpoint)
   3.107  }
   3.108  
   3.109 +// RemoveEndpoint deletes the Endpoint with the specified ID from the clientStore associated
   3.110 +// with the Context, and disassociates the Endpoint from the specified Client.
   3.111  func (c Context) RemoveEndpoint(client, endpoint uuid.ID) error {
   3.112  	if c.clients == nil {
   3.113  		return ErrNoClientStore
   3.114  	}
   3.115 -	return c.clients.RemoveEndpoint(client, endpoint)
   3.116 +	return c.clients.removeEndpoint(client, endpoint)
   3.117  }
   3.118  
   3.119 +// CheckEndpoint finds Endpoints in the clientStore associated with the Context that belong
   3.120 +// to the Client specified by the passed ID and match the URI passed. If strict is true, only
   3.121 +// exact matches for the URI will be returned. If it is false, matches are performed according
   3.122 +// to RFC 3986 Section 6.
   3.123  func (c Context) CheckEndpoint(client uuid.ID, URI string, strict bool) (bool, error) {
   3.124  	if c.clients == nil {
   3.125  		return false, ErrNoClientStore
   3.126  	}
   3.127 -	return c.clients.CheckEndpoint(client, URI, strict)
   3.128 +	return c.clients.checkEndpoint(client, URI, strict)
   3.129  }
   3.130  
   3.131 +// ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
   3.132 +// to the Client specified by the passed ID. It returns up to num endpoints, starting at offset,
   3.133 +// exclusive.
   3.134  func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
   3.135  	if c.clients == nil {
   3.136  		return []Endpoint{}, ErrNoClientStore
   3.137  	}
   3.138 -	return c.clients.ListEndpoints(client, num, offset)
   3.139 +	return c.clients.listEndpoints(client, num, offset)
   3.140  }
   3.141  
   3.142 +// CountEndpoints returns the number of Endpoints the are associated with the Client specified by the
   3.143 +// passed ID in the clientStore associated with the Context.
   3.144  func (c Context) CountEndpoints(client uuid.ID) (int64, error) {
   3.145  	if c.clients == nil {
   3.146  		return 0, ErrNoClientStore
   3.147  	}
   3.148 -	return c.clients.CountEndpoints(client)
   3.149 +	return c.clients.countEndpoints(client)
   3.150  }
   3.151  
   3.152 +// GetGrant returns the Grant specified by the provided code from the grantStore associated with the
   3.153 +// Context.
   3.154  func (c Context) GetGrant(code string) (Grant, error) {
   3.155  	if c.grants == nil {
   3.156  		return Grant{}, ErrNoGrantStore
   3.157  	}
   3.158 -	return c.grants.GetGrant(code)
   3.159 +	return c.grants.getGrant(code)
   3.160  }
   3.161  
   3.162 +// SaveGrant stores the passed Grant in the grantStore associated with the Context.
   3.163  func (c Context) SaveGrant(grant Grant) error {
   3.164  	if c.grants == nil {
   3.165  		return ErrNoGrantStore
   3.166  	}
   3.167 -	return c.grants.SaveGrant(grant)
   3.168 +	return c.grants.saveGrant(grant)
   3.169  }
   3.170  
   3.171 +// DeleteGrant removes the Grant specified by the provided code from the grantStore associated with
   3.172 +// the Context.
   3.173  func (c Context) DeleteGrant(code string) error {
   3.174  	if c.grants == nil {
   3.175  		return ErrNoGrantStore
   3.176  	}
   3.177 -	return c.grants.DeleteGrant(code)
   3.178 +	return c.grants.deleteGrant(code)
   3.179  }
   3.180  
   3.181 +// GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
   3.182 +// the Context.
   3.183  func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
   3.184  	if c.profiles == nil {
   3.185  		return Profile{}, ErrNoProfileStore
   3.186  	}
   3.187 -	return c.profiles.GetProfileByID(id)
   3.188 +	return c.profiles.getProfileByID(id)
   3.189  }
   3.190  
   3.191 +// GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
   3.192 +// with the Context.
   3.193  func (c Context) GetProfileByLogin(loginType, value string) (Profile, error) {
   3.194  	if c.profiles == nil {
   3.195  		return Profile{}, ErrNoProfileStore
   3.196  	}
   3.197 -	return c.profiles.GetProfileByLogin(loginType, value)
   3.198 +	return c.profiles.getProfileByLogin(loginType, value)
   3.199  }
   3.200  
   3.201 +// SaveProfile inserts the passed Profile into the profileStore associated with the Context.
   3.202  func (c Context) SaveProfile(profile Profile) error {
   3.203  	if c.profiles == nil {
   3.204  		return ErrNoProfileStore
   3.205  	}
   3.206 -	return c.profiles.SaveProfile(profile)
   3.207 +	return c.profiles.saveProfile(profile)
   3.208  }
   3.209  
   3.210 +// UpdateProfile applies the supplied ProfileChange to the Profile that matches the specified ID
   3.211 +// in the profileStore associated with the Context.
   3.212  func (c Context) UpdateProfile(id uuid.ID, change ProfileChange) error {
   3.213  	if c.profiles == nil {
   3.214  		return ErrNoProfileStore
   3.215  	}
   3.216 -	return c.profiles.UpdateProfile(id, change)
   3.217 +	return c.profiles.updateProfile(id, change)
   3.218  }
   3.219  
   3.220 +// UpdateProfiles applies the supplied BulkProfileChange to every Profile that matches one of the
   3.221 +// specified IDs in the profileStore associated with the Context.
   3.222  func (c Context) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
   3.223  	if c.profiles == nil {
   3.224  		return ErrNoProfileStore
   3.225  	}
   3.226 -	return c.profiles.UpdateProfiles(ids, change)
   3.227 +	return c.profiles.updateProfiles(ids, change)
   3.228  }
   3.229  
   3.230 +// DeleteProfile removes the Profile specified by the passed ID from the profileStore associated
   3.231 +// with the Context.
   3.232  func (c Context) DeleteProfile(id uuid.ID) error {
   3.233  	if c.profiles == nil {
   3.234  		return ErrNoProfileStore
   3.235  	}
   3.236 -	return c.profiles.DeleteProfile(id)
   3.237 +	return c.profiles.deleteProfile(id)
   3.238  }
   3.239  
   3.240 +// AddLogin stores the passed Login in the profileStore associated with the Context. It also associates
   3.241 +// the newly-created Login with the Orofile in login.ProfileID.
   3.242  func (c Context) AddLogin(login Login) error {
   3.243  	if c.profiles == nil {
   3.244  		return ErrNoProfileStore
   3.245  	}
   3.246 -	return c.profiles.AddLogin(login)
   3.247 +	return c.profiles.addLogin(login)
   3.248  }
   3.249  
   3.250 +// RemoveLogin removes the specified Login from the profileStore associated with the Context, provided
   3.251 +// the Login has a ProfileID property that matches the profile ID passed in. It also disassociates the
   3.252 +// deleted Login from the Profile in login.ProfileID.
   3.253  func (c Context) RemoveLogin(loginType, value string, profile uuid.ID) error {
   3.254  	if c.profiles == nil {
   3.255  		return ErrNoProfileStore
   3.256  	}
   3.257 -	return c.profiles.RemoveLogin(loginType, value, profile)
   3.258 +	return c.profiles.removeLogin(loginType, value, profile)
   3.259  }
   3.260  
   3.261 +// RecordLoginUse sets the LastUsed property of the Login specified in the profileStore associated with
   3.262 +// the Context to the value passed in as when.
   3.263  func (c Context) RecordLoginUse(loginType, value string, when time.Time) error {
   3.264  	if c.profiles == nil {
   3.265  		return ErrNoProfileStore
   3.266  	}
   3.267 -	return c.profiles.RecordLoginUse(loginType, value, when)
   3.268 +	return c.profiles.recordLoginUse(loginType, value, when)
   3.269  }
   3.270  
   3.271 +// ListLogins returns a slice of up to num Logins associated with the specified Profile from the profileStore
   3.272 +// associated with the Context, skipping offset Profiles.
   3.273  func (c Context) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
   3.274  	if c.profiles == nil {
   3.275  		return []Login{}, ErrNoProfileStore
   3.276  	}
   3.277 -	return c.profiles.ListLogins(profile, num, offset)
   3.278 +	return c.profiles.listLogins(profile, num, offset)
   3.279  }
   3.280  
   3.281 +// GetToken returns the Token specified from the tokenStore associated with the Context.
   3.282 +// If refresh is true, the token input should be compared against the refresh tokens, not the
   3.283 +// access tokens.
   3.284  func (c Context) GetToken(token string, refresh bool) (Token, error) {
   3.285  	if c.tokens == nil {
   3.286  		return Token{}, ErrNoTokenStore
   3.287  	}
   3.288 -	return c.tokens.GetToken(token, refresh)
   3.289 +	return c.tokens.getToken(token, refresh)
   3.290  }
   3.291  
   3.292 +// SaveToken stores the passed Token in the tokenStore associated with the Context.
   3.293  func (c Context) SaveToken(token Token) error {
   3.294  	if c.tokens == nil {
   3.295  		return ErrNoTokenStore
   3.296  	}
   3.297 -	return c.tokens.SaveToken(token)
   3.298 +	return c.tokens.saveToken(token)
   3.299  }
   3.300  
   3.301 +// RemoveToken removes the Token identified by the passed token string from the tokenStore associated
   3.302 +// with the Context.
   3.303  func (c Context) RemoveToken(token string) error {
   3.304  	if c.tokens == nil {
   3.305  		return ErrNoTokenStore
   3.306  	}
   3.307 -	return c.tokens.RemoveToken(token)
   3.308 +	return c.tokens.removeToken(token)
   3.309  }
   3.310  
   3.311 +// GetTokensByProfileID returns a slice of up to num Tokens with a ProfileID that matches the specified
   3.312 +// profileID from the tokenStore associated with the Context, skipping offset Tokens.
   3.313  func (c Context) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
   3.314  	if c.tokens == nil {
   3.315  		return []Token{}, ErrNoTokenStore
   3.316  	}
   3.317 -	return c.tokens.GetTokensByProfileID(profileID, num, offset)
   3.318 +	return c.tokens.getTokensByProfileID(profileID, num, offset)
   3.319  }
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/doc.go	Sun Oct 26 00:53:36 2014 -0400
     4.3 @@ -0,0 +1,16 @@
     4.4 +/*
     4.5 +Package auth provides an authentication service for managing user accounts and an OAuth2 provider.
     4.6 +
     4.7 +The service is an opinionated implementation of authentication using passphrases and the
     4.8 +code.secondbit.org/pass package to implement user credentials and accounts. Additionally, users
     4.9 +are permitted to login using their email address on record or their username interchangeably.
    4.10 +Care is also taken to be able to mitigate attacks that have already happened and plan ahead for
    4.11 +the worst case scenarios.
    4.12 +
    4.13 +An OAuth2 provider is also built-in and provided, complete with client registration and management,
    4.14 +as well as a specification-based set of handlers for managing the issuing of grants and tokens. Token
    4.15 +validiity may be asserted through an API, or a proxy service is provided for stripping auth-specific
    4.16 +information from requests and replacing it with a trusted header containing information about the user
    4.17 +and client that authorized the request.
    4.18 +*/
    4.19 +package auth
     5.1 --- a/grant.go	Wed Oct 22 00:30:28 2014 -0400
     5.2 +++ b/grant.go	Sun Oct 26 00:53:36 2014 -0400
     5.3 @@ -8,11 +8,17 @@
     5.4  )
     5.5  
     5.6  var (
     5.7 -	ErrNoGrantStore       = errors.New("no GrantStore was specified for the Context")
     5.8 -	ErrGrantNotFound      = errors.New("grant not found in GrantStore")
     5.9 -	ErrGrantAlreadyExists = errors.New("grant already exists in GrantStore")
    5.10 +	// ErrNoGrantStore is returned when a Context tries to act on a grantStore without setting one first.
    5.11 +	ErrNoGrantStore = errors.New("no grantStore was specified for the Context")
    5.12 +	// ErrGrantNotFound is returned when a Grant is requested but not found in the grantStore.
    5.13 +	ErrGrantNotFound = errors.New("grant not found in grantStore")
    5.14 +	// ErrGrantAlreadyExists is returned when a Grant is added to a grantStore, but another Grant with the
    5.15 +	// same Code already exists in the grantStore.
    5.16 +	ErrGrantAlreadyExists = errors.New("grant already exists in grantStore")
    5.17  )
    5.18  
    5.19 +// Grant represents an authorization grant made by a user to a Client, to
    5.20 +// access user data within a defined Scope for a limited amount of time.
    5.21  type Grant struct {
    5.22  	Code        string
    5.23  	Created     time.Time
    5.24 @@ -23,13 +29,13 @@
    5.25  	State       string
    5.26  }
    5.27  
    5.28 -type GrantStore interface {
    5.29 -	GetGrant(code string) (Grant, error)
    5.30 -	SaveGrant(grant Grant) error
    5.31 -	DeleteGrant(code string) error
    5.32 +type grantStore interface {
    5.33 +	getGrant(code string) (Grant, error)
    5.34 +	saveGrant(grant Grant) error
    5.35 +	deleteGrant(code string) error
    5.36  }
    5.37  
    5.38 -func (m *Memstore) GetGrant(code string) (Grant, error) {
    5.39 +func (m *memstore) getGrant(code string) (Grant, error) {
    5.40  	m.grantLock.RLock()
    5.41  	defer m.grantLock.RUnlock()
    5.42  	grant, ok := m.grants[code]
    5.43 @@ -39,7 +45,7 @@
    5.44  	return grant, nil
    5.45  }
    5.46  
    5.47 -func (m *Memstore) SaveGrant(grant Grant) error {
    5.48 +func (m *memstore) saveGrant(grant Grant) error {
    5.49  	m.grantLock.Lock()
    5.50  	defer m.grantLock.Unlock()
    5.51  	_, ok := m.grants[grant.Code]
    5.52 @@ -50,7 +56,7 @@
    5.53  	return nil
    5.54  }
    5.55  
    5.56 -func (m *Memstore) DeleteGrant(code string) error {
    5.57 +func (m *memstore) deleteGrant(code string) error {
    5.58  	m.grantLock.Lock()
    5.59  	defer m.grantLock.Unlock()
    5.60  	_, ok := m.grants[code]
     6.1 --- a/grant_test.go	Wed Oct 22 00:30:28 2014 -0400
     6.2 +++ b/grant_test.go	Sun Oct 26 00:53:36 2014 -0400
     6.3 @@ -7,7 +7,7 @@
     6.4  	"code.secondbit.org/uuid"
     6.5  )
     6.6  
     6.7 -var grantStores = []GrantStore{NewMemstore()}
     6.8 +var grantStores = []grantStore{NewMemstore()}
     6.9  
    6.10  func compareGrants(grant1, grant2 Grant) (success bool, field string, grant1val, grant2val interface{}) {
    6.11  	if grant1.Code != grant2.Code {
    6.12 @@ -46,15 +46,15 @@
    6.13  		State:       "state",
    6.14  	}
    6.15  	for _, store := range grantStores {
    6.16 -		err := store.SaveGrant(grant)
    6.17 +		err := store.saveGrant(grant)
    6.18  		if err != nil {
    6.19  			t.Errorf("Error saving grant to %T: %s", store, err)
    6.20  		}
    6.21 -		err = store.SaveGrant(grant)
    6.22 +		err = store.saveGrant(grant)
    6.23  		if err != ErrGrantAlreadyExists {
    6.24  			t.Errorf("Expected ErrGrantAlreadyExists from %T, got %+v", store, err)
    6.25  		}
    6.26 -		retrieved, err := store.GetGrant(grant.Code)
    6.27 +		retrieved, err := store.getGrant(grant.Code)
    6.28  		if err != nil {
    6.29  			t.Errorf("Error retrieving grant from %T: %s", store, err)
    6.30  		}
    6.31 @@ -62,15 +62,15 @@
    6.32  		if !match {
    6.33  			t.Errorf("Expected `%v` in the `%s` field of grant retrieved from %T, got `%v`", expectation, field, store, result)
    6.34  		}
    6.35 -		err = store.DeleteGrant(grant.Code)
    6.36 +		err = store.deleteGrant(grant.Code)
    6.37  		if err != nil {
    6.38  			t.Errorf("Error removing grant from %T: %s", store, err)
    6.39  		}
    6.40 -		retrieved, err = store.GetGrant(grant.Code)
    6.41 +		retrieved, err = store.getGrant(grant.Code)
    6.42  		if err != ErrGrantNotFound {
    6.43  			t.Errorf("Expected ErrGrantNotFound from %T, got %+v and %+v", store, retrieved, err)
    6.44  		}
    6.45 -		err = store.DeleteGrant(grant.Code)
    6.46 +		err = store.deleteGrant(grant.Code)
    6.47  		if err != ErrGrantNotFound {
    6.48  			t.Errorf("Expected ErrGrantNotFound from %T, got %+v", store, err)
    6.49  		}
     7.1 --- a/http.go	Wed Oct 22 00:30:28 2014 -0400
     7.2 +++ b/http.go	Sun Oct 26 00:53:36 2014 -0400
     7.3 @@ -8,6 +8,8 @@
     7.4  
     7.5  const getGrantTemplateName = "get_grant"
     7.6  
     7.7 +// GetGrantHandler presents and processes the page for asking a user to grant access
     7.8 +// to their data. See RFC 6749, Section 4.1.
     7.9  func GetGrantHandler(w http.ResponseWriter, r *http.Request, context Context) {
    7.10  	if r.URL.Query().Get("client_id") == "" {
    7.11  		w.WriteHeader(http.StatusBadRequest)
     8.1 --- a/memstore.go	Wed Oct 22 00:30:28 2014 -0400
     8.2 +++ b/memstore.go	Sun Oct 26 00:53:36 2014 -0400
     8.3 @@ -6,7 +6,7 @@
     8.4  	"code.secondbit.org/uuid"
     8.5  )
     8.6  
     8.7 -type Memstore struct {
     8.8 +type memstore struct {
     8.9  	tokens             map[string]Token
    8.10  	refreshTokenLookup map[string]string
    8.11  	profileTokenLookup map[string][]string
    8.12 @@ -30,8 +30,12 @@
    8.13  	loginLock          sync.RWMutex
    8.14  }
    8.15  
    8.16 -func NewMemstore() *Memstore {
    8.17 -	return &Memstore{
    8.18 +// NewMemstore returns an in-memory version of our datastores,
    8.19 +// which is handy for tests. Though the implementation is concurrency-safe,
    8.20 +// if makes no attempt to persist the data, and therefore it is inadvisable
    8.21 +// to use it in a production setting.
    8.22 +func NewMemstore() *memstore {
    8.23 +	return &memstore{
    8.24  		tokens:              map[string]Token{},
    8.25  		refreshTokenLookup:  map[string]string{},
    8.26  		profileTokenLookup:  map[string][]string{},
    8.27 @@ -45,7 +49,7 @@
    8.28  	}
    8.29  }
    8.30  
    8.31 -func (m *Memstore) lookupTokenByRefresh(token string) (string, error) {
    8.32 +func (m *memstore) lookupTokenByRefresh(token string) (string, error) {
    8.33  	m.tokenLock.RLock()
    8.34  	defer m.tokenLock.RUnlock()
    8.35  	t, ok := m.refreshTokenLookup[token]
    8.36 @@ -55,13 +59,13 @@
    8.37  	return t, nil
    8.38  }
    8.39  
    8.40 -func (m *Memstore) lookupTokensByProfileID(id string) ([]string, error) {
    8.41 +func (m *memstore) lookupTokensByProfileID(id string) ([]string, error) {
    8.42  	m.tokenLock.RLock()
    8.43  	defer m.tokenLock.RUnlock()
    8.44  	return m.profileTokenLookup[id], nil
    8.45  }
    8.46  
    8.47 -func (m *Memstore) lookupClientsByProfileID(id string) []uuid.ID {
    8.48 +func (m *memstore) lookupClientsByProfileID(id string) []uuid.ID {
    8.49  	m.clientLock.RLock()
    8.50  	defer m.clientLock.RUnlock()
    8.51  	c, ok := m.profileClientLookup[id]
     9.1 --- a/profile.go	Wed Oct 22 00:30:28 2014 -0400
     9.2 +++ b/profile.go	Sun Oct 26 00:53:36 2014 -0400
     9.3 @@ -8,24 +8,46 @@
     9.4  )
     9.5  
     9.6  const (
     9.7 +	// MinPassphraseLength is the minimum length, in bytes, of a passphrase, exclusive.
     9.8  	MinPassphraseLength = 6
     9.9 +	// MaxPassphraseLength is the maximum length, in bytes, of a passphrase, exclusive.
    9.10  	MaxPassphraseLength = 64
    9.11  )
    9.12  
    9.13  var (
    9.14 -	ErrNoProfileStore       = errors.New("no ProfileStore was specified for the Context")
    9.15 -	ErrProfileAlreadyExists = errors.New("profile already exists in ProfileStore")
    9.16 -	ErrProfileNotFound      = errors.New("profile not found in ProfileStore")
    9.17 -	ErrLoginAlreadyExists   = errors.New("login already exists in ProfileStore")
    9.18 -	ErrLoginNotFound        = errors.New("login not found in ProfileStore")
    9.19 +	// ErrNoProfileStore is returned when a Context tries to act on a profileStore without setting one first.
    9.20 +	ErrNoProfileStore = errors.New("no profileStore was specified for the Context")
    9.21 +	// ErrProfileAlreadyExists is returned when a Profile is added to a profileStore, but another Profile with
    9.22 +	// the same ID already exists in the profileStore.
    9.23 +	ErrProfileAlreadyExists = errors.New("profile already exists in profileStore")
    9.24 +	// ErrProfileNotFound is returned when a Profile is requested but not found in the profileStore.
    9.25 +	ErrProfileNotFound = errors.New("profile not found in profileStore")
    9.26 +	// ErrLoginAlreadyExists is returned when a Login is added to a profileStore, but another Login with the same
    9.27 +	// Type and Value already exists in the profileStore.
    9.28 +	ErrLoginAlreadyExists = errors.New("login already exists in profileStore")
    9.29 +	// ErrLoginNotFound is returned when a Login is requested but not found in the profileStore.
    9.30 +	ErrLoginNotFound = errors.New("login not found in profileStore")
    9.31  
    9.32 -	ErrMissingPassphrase             = errors.New("missing passphrase")
    9.33 -	ErrMissingPassphraseReset        = errors.New("missing passphrase reset")
    9.34 +	// ErrMissingPassphrase is returned when a ProfileChange is validated but does not contain a
    9.35 +	// Passphrase, and requires one.
    9.36 +	ErrMissingPassphrase = errors.New("missing passphrase")
    9.37 +	// ErrMissingPassphraseReset is returned when a ProfileChange is validated but does not contain
    9.38 +	// a PassphraseReset, and requires one.
    9.39 +	ErrMissingPassphraseReset = errors.New("missing passphrase reset")
    9.40 +	// ErrMissingPassphraseResetCreated is returned when a ProfileChange is validated but does not
    9.41 +	// contain a PassphraseResetCreated, and requires one.
    9.42  	ErrMissingPassphraseResetCreated = errors.New("missing passphrase reset created timestamp")
    9.43 -	ErrPassphraseTooShort            = errors.New("passphrase too short")
    9.44 -	ErrPassphraseTooLong             = errors.New("passphrase too long")
    9.45 +	// ErrPassphraseTooShort is returned when a ProfileChange is validated and contains a Passphrase,
    9.46 +	// but the Passphrase is shorter than MinPassphraseLength.
    9.47 +	ErrPassphraseTooShort = errors.New("passphrase too short")
    9.48 +	// ErrPassphraseTooLong is returned when a ProfileChange is validated and contains a Passphrase,
    9.49 +	// but the Passphrase is longer than MaxPassphraseLength.
    9.50 +	ErrPassphraseTooLong = errors.New("passphrase too long")
    9.51  )
    9.52  
    9.53 +// Profile represents a single user of the service,
    9.54 +// including their authentication information, but not
    9.55 +// including their username or email.
    9.56  type Profile struct {
    9.57  	ID                     uuid.ID
    9.58  	Name                   string
    9.59 @@ -41,6 +63,8 @@
    9.60  	LastSeen               time.Time
    9.61  }
    9.62  
    9.63 +// ApplyChange applies the properties of the passed ProfileChange
    9.64 +// to the Profile it is called on.
    9.65  func (p *Profile) ApplyChange(change ProfileChange) {
    9.66  	if change.Name != nil {
    9.67  		p.Name = *change.Name
    9.68 @@ -74,12 +98,15 @@
    9.69  	}
    9.70  }
    9.71  
    9.72 +// ApplyBulkChange applies the properties of the passed BulkProfileChange
    9.73 +// to the Profile it is called on.
    9.74  func (p *Profile) ApplyBulkChange(change BulkProfileChange) {
    9.75  	if change.Compromised != nil {
    9.76  		p.Compromised = *change.Compromised
    9.77  	}
    9.78  }
    9.79  
    9.80 +// ProfileChange represents a single atomic change to a Profile's mutable data.
    9.81  type ProfileChange struct {
    9.82  	Name                   *string
    9.83  	Passphrase             *string
    9.84 @@ -93,6 +120,10 @@
    9.85  	LastSeen               *time.Time
    9.86  }
    9.87  
    9.88 +// Validate checks the ProfileChange it is called on
    9.89 +// and asserts its internal validity, or lack thereof.
    9.90 +// A descriptive error will be returned in the case of
    9.91 +// an invalid change.
    9.92  func (c ProfileChange) Validate() error {
    9.93  	if c.Name == nil && c.Passphrase == nil && c.Iterations == nil && c.Salt == nil && c.PassphraseScheme == nil && c.Compromised == nil && c.LockedUntil == nil && c.PassphraseReset == nil && c.PassphraseResetCreated == nil && c.LastSeen == nil {
    9.94  		return ErrEmptyChange
    9.95 @@ -121,10 +152,17 @@
    9.96  	return nil
    9.97  }
    9.98  
    9.99 +// BulkProfileChange represents a single atomic change to many Profiles' mutable data.
   9.100 +// It is a subset of a ProfileChange, as it doesn't make sense to mutate some of the
   9.101 +// ProfileChange values across many Profiles all at once.
   9.102  type BulkProfileChange struct {
   9.103  	Compromised *bool
   9.104  }
   9.105  
   9.106 +// Validate checks the BulkProfileChange it is called on
   9.107 +// and asserts its internal validity, or lack thereof.
   9.108 +// A descriptive error will be returned in the case of an
   9.109 +// invalid change.
   9.110  func (b BulkProfileChange) Validate() error {
   9.111  	if b.Compromised == nil {
   9.112  		return ErrEmptyChange
   9.113 @@ -132,6 +170,9 @@
   9.114  	return nil
   9.115  }
   9.116  
   9.117 +// Login represents a single human-friendly identifier for
   9.118 +// a given Profile that can be used to log into that Profile.
   9.119 +// Each Profile may only have one Login for each Type.
   9.120  type Login struct {
   9.121  	Type      string
   9.122  	Value     string
   9.123 @@ -140,21 +181,21 @@
   9.124  	LastUsed  time.Time
   9.125  }
   9.126  
   9.127 -type ProfileStore interface {
   9.128 -	GetProfileByID(id uuid.ID) (Profile, error)
   9.129 -	GetProfileByLogin(loginType, value string) (Profile, error)
   9.130 -	SaveProfile(profile Profile) error
   9.131 -	UpdateProfile(id uuid.ID, change ProfileChange) error
   9.132 -	UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error
   9.133 -	DeleteProfile(id uuid.ID) error
   9.134 +type profileStore interface {
   9.135 +	getProfileByID(id uuid.ID) (Profile, error)
   9.136 +	getProfileByLogin(loginType, value string) (Profile, error)
   9.137 +	saveProfile(profile Profile) error
   9.138 +	updateProfile(id uuid.ID, change ProfileChange) error
   9.139 +	updateProfiles(ids []uuid.ID, change BulkProfileChange) error
   9.140 +	deleteProfile(id uuid.ID) error
   9.141  
   9.142 -	AddLogin(login Login) error
   9.143 -	RemoveLogin(loginType, value string, profile uuid.ID) error
   9.144 -	RecordLoginUse(loginType, value string, when time.Time) error
   9.145 -	ListLogins(profile uuid.ID, num, offset int) ([]Login, error)
   9.146 +	addLogin(login Login) error
   9.147 +	removeLogin(loginType, value string, profile uuid.ID) error
   9.148 +	recordLoginUse(loginType, value string, when time.Time) error
   9.149 +	listLogins(profile uuid.ID, num, offset int) ([]Login, error)
   9.150  }
   9.151  
   9.152 -func (m *Memstore) GetProfileByID(id uuid.ID) (Profile, error) {
   9.153 +func (m *memstore) getProfileByID(id uuid.ID) (Profile, error) {
   9.154  	m.profileLock.RLock()
   9.155  	defer m.profileLock.RUnlock()
   9.156  	p, ok := m.profiles[id.String()]
   9.157 @@ -164,7 +205,7 @@
   9.158  	return p, nil
   9.159  }
   9.160  
   9.161 -func (m *Memstore) GetProfileByLogin(loginType, value string) (Profile, error) {
   9.162 +func (m *memstore) getProfileByLogin(loginType, value string) (Profile, error) {
   9.163  	m.loginLock.RLock()
   9.164  	defer m.loginLock.RUnlock()
   9.165  	login, ok := m.logins[loginType+":"+value]
   9.166 @@ -180,7 +221,7 @@
   9.167  	return profile, nil
   9.168  }
   9.169  
   9.170 -func (m *Memstore) SaveProfile(profile Profile) error {
   9.171 +func (m *memstore) saveProfile(profile Profile) error {
   9.172  	m.profileLock.Lock()
   9.173  	defer m.profileLock.Unlock()
   9.174  	_, ok := m.profiles[profile.ID.String()]
   9.175 @@ -191,7 +232,7 @@
   9.176  	return nil
   9.177  }
   9.178  
   9.179 -func (m *Memstore) UpdateProfile(id uuid.ID, change ProfileChange) error {
   9.180 +func (m *memstore) updateProfile(id uuid.ID, change ProfileChange) error {
   9.181  	m.profileLock.Lock()
   9.182  	defer m.profileLock.Unlock()
   9.183  	p, ok := m.profiles[id.String()]
   9.184 @@ -203,7 +244,7 @@
   9.185  	return nil
   9.186  }
   9.187  
   9.188 -func (m *Memstore) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
   9.189 +func (m *memstore) updateProfiles(ids []uuid.ID, change BulkProfileChange) error {
   9.190  	m.profileLock.Lock()
   9.191  	defer m.profileLock.Unlock()
   9.192  	for id, profile := range m.profiles {
   9.193 @@ -218,7 +259,7 @@
   9.194  	return nil
   9.195  }
   9.196  
   9.197 -func (m *Memstore) DeleteProfile(id uuid.ID) error {
   9.198 +func (m *memstore) deleteProfile(id uuid.ID) error {
   9.199  	m.profileLock.Lock()
   9.200  	defer m.profileLock.Unlock()
   9.201  	_, ok := m.profiles[id.String()]
   9.202 @@ -229,7 +270,7 @@
   9.203  	return nil
   9.204  }
   9.205  
   9.206 -func (m *Memstore) AddLogin(login Login) error {
   9.207 +func (m *memstore) addLogin(login Login) error {
   9.208  	m.loginLock.Lock()
   9.209  	defer m.loginLock.Unlock()
   9.210  	_, ok := m.logins[login.Type+":"+login.Value]
   9.211 @@ -241,7 +282,7 @@
   9.212  	return nil
   9.213  }
   9.214  
   9.215 -func (m *Memstore) RemoveLogin(loginType, value string, profile uuid.ID) error {
   9.216 +func (m *memstore) removeLogin(loginType, value string, profile uuid.ID) error {
   9.217  	m.loginLock.Lock()
   9.218  	defer m.loginLock.Unlock()
   9.219  	l, ok := m.logins[loginType+":"+value]
   9.220 @@ -265,7 +306,7 @@
   9.221  	return nil
   9.222  }
   9.223  
   9.224 -func (m *Memstore) RecordLoginUse(loginType, value string, when time.Time) error {
   9.225 +func (m *memstore) recordLoginUse(loginType, value string, when time.Time) error {
   9.226  	m.loginLock.Lock()
   9.227  	defer m.loginLock.Unlock()
   9.228  	l, ok := m.logins[loginType+":"+value]
   9.229 @@ -277,7 +318,7 @@
   9.230  	return nil
   9.231  }
   9.232  
   9.233 -func (m *Memstore) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
   9.234 +func (m *memstore) listLogins(profile uuid.ID, num, offset int) ([]Login, error) {
   9.235  	m.loginLock.RLock()
   9.236  	defer m.loginLock.RUnlock()
   9.237  	ids, ok := m.profileLoginLookup[profile.String()]
    10.1 --- a/profile_test.go	Wed Oct 22 00:30:28 2014 -0400
    10.2 +++ b/profile_test.go	Sun Oct 26 00:53:36 2014 -0400
    10.3 @@ -21,7 +21,7 @@
    10.4  	profileChangeLastSeen
    10.5  )
    10.6  
    10.7 -var profileStores = []ProfileStore{NewMemstore()}
    10.8 +var profileStores = []profileStore{NewMemstore()}
    10.9  
   10.10  func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) {
   10.11  	if !profile1.ID.Equal(profile2.ID) {
   10.12 @@ -99,15 +99,15 @@
   10.13  		LastSeen:               time.Now(),
   10.14  	}
   10.15  	for _, store := range profileStores {
   10.16 -		err := store.SaveProfile(profile)
   10.17 +		err := store.saveProfile(profile)
   10.18  		if err != nil {
   10.19  			t.Errorf("Error saving profile to %T: %s", store, err)
   10.20  		}
   10.21 -		err = store.SaveProfile(profile)
   10.22 +		err = store.saveProfile(profile)
   10.23  		if err != ErrProfileAlreadyExists {
   10.24  			t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
   10.25  		}
   10.26 -		retrieved, err := store.GetProfileByID(profile.ID)
   10.27 +		retrieved, err := store.getProfileByID(profile.ID)
   10.28  		if err != nil {
   10.29  			t.Errorf("Error retrieving profile from %T: %s", store, err)
   10.30  		}
   10.31 @@ -115,15 +115,15 @@
   10.32  		if !match {
   10.33  			t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
   10.34  		}
   10.35 -		err = store.DeleteProfile(profile.ID)
   10.36 +		err = store.deleteProfile(profile.ID)
   10.37  		if err != nil {
   10.38  			t.Errorf("Error removing profile from %T: %s", store, err)
   10.39  		}
   10.40 -		retrieved, err = store.GetProfileByID(profile.ID)
   10.41 +		retrieved, err = store.getProfileByID(profile.ID)
   10.42  		if err != ErrProfileNotFound {
   10.43  			t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
   10.44  		}
   10.45 -		err = store.DeleteProfile(profile.ID)
   10.46 +		err = store.deleteProfile(profile.ID)
   10.47  		if err != ErrProfileNotFound {
   10.48  			t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err)
   10.49  		}
   10.50 @@ -213,15 +213,15 @@
   10.51  			t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
   10.52  		}
   10.53  		for _, store := range profileStores {
   10.54 -			err := store.SaveProfile(profile)
   10.55 +			err := store.saveProfile(profile)
   10.56  			if err != nil {
   10.57  				t.Errorf("Error saving profile in %T: %s", store, err)
   10.58  			}
   10.59 -			err = store.UpdateProfile(profile.ID, change)
   10.60 +			err = store.updateProfile(profile.ID, change)
   10.61  			if err != nil {
   10.62  				t.Errorf("Error updating profile in %T: %s", store, err)
   10.63  			}
   10.64 -			retrieved, err := store.GetProfileByID(profile.ID)
   10.65 +			retrieved, err := store.getProfileByID(profile.ID)
   10.66  			if err != nil {
   10.67  				t.Errorf("Error getting profile from %T: %s", store, err)
   10.68  			}
   10.69 @@ -229,11 +229,11 @@
   10.70  			if !match {
   10.71  				t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
   10.72  			}
   10.73 -			err = store.DeleteProfile(profile.ID)
   10.74 +			err = store.deleteProfile(profile.ID)
   10.75  			if err != nil {
   10.76  				t.Errorf("Error deleting profile from %T: %s", store, err)
   10.77  			}
   10.78 -			err = store.UpdateProfile(profile.ID, change)
   10.79 +			err = store.updateProfile(profile.ID, change)
   10.80  			if err != ErrProfileNotFound {
   10.81  				t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store)
   10.82  			}
   10.83 @@ -256,25 +256,25 @@
   10.84  		Compromised: &truth,
   10.85  	}
   10.86  	for _, store := range profileStores {
   10.87 -		err := store.SaveProfile(profile1)
   10.88 +		err := store.saveProfile(profile1)
   10.89  		if err != nil {
   10.90  			t.Errorf("Error saving profile in %T: %s", store, err)
   10.91  		}
   10.92 -		err = store.SaveProfile(profile2)
   10.93 +		err = store.saveProfile(profile2)
   10.94  		if err != nil {
   10.95  			t.Errorf("Error saving profile in %T: %s", store, err)
   10.96  		}
   10.97 -		err = store.SaveProfile(profile3)
   10.98 +		err = store.saveProfile(profile3)
   10.99  		if err != nil {
  10.100  			t.Errorf("Error saving profile in %T: %s", store, err)
  10.101  		}
  10.102 -		err = store.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
  10.103 +		err = store.updateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
  10.104  		if err != nil {
  10.105  			t.Errorf("Error updating profile in %T: %s", store, err)
  10.106  		}
  10.107  		profile1.Compromised = truth
  10.108  		profile3.Compromised = truth
  10.109 -		retrieved, err := store.GetProfileByID(profile1.ID)
  10.110 +		retrieved, err := store.getProfileByID(profile1.ID)
  10.111  		if err != nil {
  10.112  			t.Errorf("Error getting profile from %T: %s", store, err)
  10.113  		}
  10.114 @@ -282,7 +282,7 @@
  10.115  		if !match {
  10.116  			t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
  10.117  		}
  10.118 -		retrieved, err = store.GetProfileByID(profile2.ID)
  10.119 +		retrieved, err = store.getProfileByID(profile2.ID)
  10.120  		if err != nil {
  10.121  			t.Errorf("Error getting profile from %T: %s", store, err)
  10.122  		}
  10.123 @@ -290,7 +290,7 @@
  10.124  		if !match {
  10.125  			t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
  10.126  		}
  10.127 -		retrieved, err = store.GetProfileByID(profile3.ID)
  10.128 +		retrieved, err = store.getProfileByID(profile3.ID)
  10.129  		if err != nil {
  10.130  			t.Errorf("Error getting profile from %T: %s", store, err)
  10.131  		}
  10.132 @@ -311,15 +311,15 @@
  10.133  		LastUsed:  time.Now().Add(-1 * time.Minute),
  10.134  	}
  10.135  	for _, store := range profileStores {
  10.136 -		err := store.AddLogin(login)
  10.137 +		err := store.addLogin(login)
  10.138  		if err != nil {
  10.139  			t.Errorf("Error adding login to %T: %s", store, err)
  10.140  		}
  10.141 -		err = store.AddLogin(login)
  10.142 +		err = store.addLogin(login)
  10.143  		if err != ErrLoginAlreadyExists {
  10.144  			t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
  10.145  		}
  10.146 -		retrieved, err := store.ListLogins(login.ProfileID, 10, 0)
  10.147 +		retrieved, err := store.listLogins(login.ProfileID, 10, 0)
  10.148  		if err != nil {
  10.149  			t.Errorf("Error retrieving logins from %T: %s", store, err)
  10.150  		}
  10.151 @@ -331,12 +331,12 @@
  10.152  			t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
  10.153  		}
  10.154  		lastUsed := time.Now()
  10.155 -		err = store.RecordLoginUse(login.Type, login.Value, lastUsed)
  10.156 +		err = store.recordLoginUse(login.Type, login.Value, lastUsed)
  10.157  		if err != nil {
  10.158  			t.Errorf("Error recording use of login to %T: %s", store, err)
  10.159  		}
  10.160  		login.LastUsed = lastUsed
  10.161 -		retrieved, err = store.ListLogins(login.ProfileID, 10, 0)
  10.162 +		retrieved, err = store.listLogins(login.ProfileID, 10, 0)
  10.163  		if err != nil {
  10.164  			t.Errorf("Error retrieving logins from %T: %s", store, err)
  10.165  		}
  10.166 @@ -347,15 +347,15 @@
  10.167  		if !match {
  10.168  			t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
  10.169  		}
  10.170 -		err = store.RemoveLogin(login.Type, login.Value, login.ProfileID)
  10.171 +		err = store.removeLogin(login.Type, login.Value, login.ProfileID)
  10.172  		if err != nil {
  10.173  			t.Errorf("Error removing login from %T: %s", store, err)
  10.174  		}
  10.175 -		retrieved, err = store.ListLogins(login.ProfileID, 10, 0)
  10.176 +		retrieved, err = store.listLogins(login.ProfileID, 10, 0)
  10.177  		if len(retrieved) != 0 {
  10.178  			t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
  10.179  		}
  10.180 -		err = store.RemoveLogin(login.Type, login.Value, login.ProfileID)
  10.181 +		err = store.removeLogin(login.Type, login.Value, login.ProfileID)
  10.182  		if err != ErrLoginNotFound {
  10.183  			t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
  10.184  		}
  10.185 @@ -386,15 +386,15 @@
  10.186  		LastUsed:  time.Now().Add(-1 * time.Minute),
  10.187  	}
  10.188  	for _, store := range profileStores {
  10.189 -		err := store.SaveProfile(profile)
  10.190 +		err := store.saveProfile(profile)
  10.191  		if err != nil {
  10.192  			t.Errorf("Error saving profile in %T: %s", store, err)
  10.193  		}
  10.194 -		err = store.AddLogin(login)
  10.195 +		err = store.addLogin(login)
  10.196  		if err != nil {
  10.197  			t.Errorf("Error storing login in %T: %s", store, err)
  10.198  		}
  10.199 -		retrieved, err := store.GetProfileByLogin(login.Type, login.Value)
  10.200 +		retrieved, err := store.getProfileByLogin(login.Type, login.Value)
  10.201  		if err != nil {
  10.202  			t.Errorf("Error retrieving profile by login from %T: %s", store, err)
  10.203  		}
    11.1 --- a/token.go	Wed Oct 22 00:30:28 2014 -0400
    11.2 +++ b/token.go	Sun Oct 26 00:53:36 2014 -0400
    11.3 @@ -8,11 +8,17 @@
    11.4  )
    11.5  
    11.6  var (
    11.7 -	ErrNoTokenStore       = errors.New("no TokenStore was specified for the Context")
    11.8 -	ErrTokenNotFound      = errors.New("token not found in TokenStore")
    11.9 -	ErrTokenAlreadyExists = errors.New("token already exists in TokenStore")
   11.10 +	// ErrNoTokenStore is returned when a Context tries to act on a tokenStore without setting one first.
   11.11 +	ErrNoTokenStore = errors.New("no tokenStore was specified for the Context")
   11.12 +	// ErrTokenNotFound is returned when a Token is requested but not found in a tokenStore.
   11.13 +	ErrTokenNotFound = errors.New("token not found in tokenStore")
   11.14 +	// ErrTokenAlreadyExists is returned when a Token is added to a tokenStore, but another Token with
   11.15 +	// the same AccessToken property already exists in the tokenStore.
   11.16 +	ErrTokenAlreadyExists = errors.New("token already exists in tokenStore")
   11.17  )
   11.18  
   11.19 +// Token represents an access and/or refresh token that the Client can use to access user data
   11.20 +// or obtain a new access token.
   11.21  type Token struct {
   11.22  	AccessToken  string
   11.23  	RefreshToken string
   11.24 @@ -23,14 +29,14 @@
   11.25  	ProfileID    uuid.ID
   11.26  }
   11.27  
   11.28 -type TokenStore interface {
   11.29 -	GetToken(token string, refresh bool) (Token, error)
   11.30 -	SaveToken(token Token) error
   11.31 -	RemoveToken(token string) error
   11.32 -	GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
   11.33 +type tokenStore interface {
   11.34 +	getToken(token string, refresh bool) (Token, error)
   11.35 +	saveToken(token Token) error
   11.36 +	removeToken(token string) error
   11.37 +	getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
   11.38  }
   11.39  
   11.40 -func (m *Memstore) GetToken(token string, refresh bool) (Token, error) {
   11.41 +func (m *memstore) getToken(token string, refresh bool) (Token, error) {
   11.42  	if refresh {
   11.43  		t, err := m.lookupTokenByRefresh(token)
   11.44  		if err != nil {
   11.45 @@ -47,7 +53,7 @@
   11.46  	return result, nil
   11.47  }
   11.48  
   11.49 -func (m *Memstore) SaveToken(token Token) error {
   11.50 +func (m *memstore) saveToken(token Token) error {
   11.51  	m.tokenLock.Lock()
   11.52  	defer m.tokenLock.Unlock()
   11.53  	_, ok := m.tokens[token.AccessToken]
   11.54 @@ -66,7 +72,7 @@
   11.55  	return nil
   11.56  }
   11.57  
   11.58 -func (m *Memstore) RemoveToken(token string) error {
   11.59 +func (m *memstore) removeToken(token string) error {
   11.60  	m.tokenLock.Lock()
   11.61  	defer m.tokenLock.Unlock()
   11.62  	t, ok := m.tokens[token]
   11.63 @@ -90,7 +96,7 @@
   11.64  	return nil
   11.65  }
   11.66  
   11.67 -func (m *Memstore) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
   11.68 +func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
   11.69  	ids, err := m.lookupTokensByProfileID(profileID.String())
   11.70  	if err != nil {
   11.71  		return []Token{}, err
   11.72 @@ -104,7 +110,7 @@
   11.73  	}
   11.74  	tokens := []Token{}
   11.75  	for _, id := range ids {
   11.76 -		token, err := m.GetToken(id, false)
   11.77 +		token, err := m.getToken(id, false)
   11.78  		if err != nil {
   11.79  			return []Token{}, err
   11.80  		}
    12.1 --- a/token_test.go	Wed Oct 22 00:30:28 2014 -0400
    12.2 +++ b/token_test.go	Sun Oct 26 00:53:36 2014 -0400
    12.3 @@ -7,7 +7,7 @@
    12.4  	"code.secondbit.org/uuid"
    12.5  )
    12.6  
    12.7 -var tokenStores = []TokenStore{NewMemstore()}
    12.8 +var tokenStores = []tokenStore{NewMemstore()}
    12.9  
   12.10  func compareTokens(token1, token2 Token) (success bool, field string, val1, val2 interface{}) {
   12.11  	if token1.AccessToken != token2.AccessToken {
   12.12 @@ -46,15 +46,15 @@
   12.13  		ProfileID:    uuid.NewID(),
   12.14  	}
   12.15  	for _, store := range tokenStores {
   12.16 -		err := store.SaveToken(token)
   12.17 +		err := store.saveToken(token)
   12.18  		if err != nil {
   12.19  			t.Errorf("Error saving token to %T: %s", store, err)
   12.20  		}
   12.21 -		err = store.SaveToken(token)
   12.22 +		err = store.saveToken(token)
   12.23  		if err != ErrTokenAlreadyExists {
   12.24  			t.Errorf("Expected ErrTokenAlreadyExists from %T, got %s", store, err)
   12.25  		}
   12.26 -		retrievedAccess, err := store.GetToken(token.AccessToken, false)
   12.27 +		retrievedAccess, err := store.getToken(token.AccessToken, false)
   12.28  		if err != nil {
   12.29  			t.Errorf("Error retrieving token from %T: %s", store, err)
   12.30  		}
   12.31 @@ -62,7 +62,7 @@
   12.32  		if !success {
   12.33  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   12.34  		}
   12.35 -		retrievedRefresh, err := store.GetToken(token.RefreshToken, true)
   12.36 +		retrievedRefresh, err := store.getToken(token.RefreshToken, true)
   12.37  		if err != nil {
   12.38  			t.Errorf("Error retrieving refresh token from %T: %s", store, err)
   12.39  		}
   12.40 @@ -70,7 +70,7 @@
   12.41  		if !success {
   12.42  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   12.43  		}
   12.44 -		retrievedProfile, err := store.GetTokensByProfileID(token.ProfileID, 25, 0)
   12.45 +		retrievedProfile, err := store.getTokensByProfileID(token.ProfileID, 25, 0)
   12.46  		if err != nil {
   12.47  			t.Errorf("Error retrieving token by profile from %T: %s", store, err)
   12.48  		}
   12.49 @@ -81,26 +81,26 @@
   12.50  		if !success {
   12.51  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   12.52  		}
   12.53 -		err = store.RemoveToken(token.AccessToken)
   12.54 +		err = store.removeToken(token.AccessToken)
   12.55  		if err != nil {
   12.56  			t.Errorf("Error removing token from %T: %s", store, err)
   12.57  		}
   12.58 -		_, err = store.GetToken(token.AccessToken, false)
   12.59 +		_, err = store.getToken(token.AccessToken, false)
   12.60  		if err != ErrTokenNotFound {
   12.61  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   12.62  		}
   12.63 -		_, err = store.GetToken(token.RefreshToken, true)
   12.64 +		_, err = store.getToken(token.RefreshToken, true)
   12.65  		if err != ErrTokenNotFound {
   12.66  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   12.67  		}
   12.68 -		retrievedProfile, err = store.GetTokensByProfileID(token.ProfileID, 25, 0)
   12.69 +		retrievedProfile, err = store.getTokensByProfileID(token.ProfileID, 25, 0)
   12.70  		if err != nil {
   12.71  			t.Errorf("Error retrieving token by profile from %T: %s", store, err)
   12.72  		}
   12.73  		if len(retrievedProfile) != 0 {
   12.74  			t.Errorf("Expected list of 0 tokens from %T, got %+v", store, retrievedProfile)
   12.75  		}
   12.76 -		err = store.RemoveToken(token.AccessToken)
   12.77 +		err = store.removeToken(token.AccessToken)
   12.78  		if err != ErrTokenNotFound {
   12.79  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   12.80  		}