Building the service

At the end of the day, whatever other dark magic is going on in our architecture, it will come down to some Go method being called, doing some work, and returning a result. So the next thing we are going to do is define and implement the Vault service itself.

Inside the vault folder, add the following code to a new service.go file:

// Service provides password hashing capabilities. 
type Service interface { 
  Hash(ctx context.Context, password string) (string,
    error) 
  Validate(ctx context.Context, password, hash string)
    (bool, error) 
} 

This interface defines the service.

Tip

You might think that VaultService would be a better name than just Service, but remember that since this is a Go package, it will been seen externally as vault.Service, which reads nicely.

We define our two methods: Hash and Validate. Each takes context.Context as the first argument, followed by normal string arguments. The responses are normal Go types as well: string, bool, and error.

Tip

Some libraries may still require the old context dependency, golang.org/x/net/context, rather than the context package that was made available first in Go 1.7. Watch out for errors complaining about mixed use and make sure you're importing the right one.

Part of designing micro-services is being careful about where state is stored. Even though you will implement the methods of a service in a single file, with access to global variables, you should never use them to store the per-request or even per-service state. It's important to remember that each service is likely to be running on many physical machines multiple times, each with no access to the others' global variables.

In this spirit, we are going to implement our service using an empty struct, essentially a neat idiomatic Go trick to group methods together in order to implement an interface without storing any state in the object itself. To service.go, add the following struct:

type vaultService struct{} 

Tip

If the implementation did require any dependencies (such as a database connection or a configuration object), you could store them inside the struct and use the method receivers in your function bodies.

Starting with tests

Where possible, starting by writing test code has many advantages that usually end up increasing the quality and maintainability of your code. We are going to write a unit test that will use our new service to hash and then validate a password.

Create a new file called service_test.go and add the following code:

package vault 
import ( 
  "testing" 
  "golang.org/x/net/context" 
) 
func TestHasherService(t *testing.T) { 
  srv := NewService() 
  ctx := context.Background() 
  h, err := srv.Hash(ctx, "password") 
  if err != nil { 
    t.Errorf("Hash: %s", err) 
  } 
  ok, err := srv.Validate(ctx, "password", h) 
  if err != nil { 
    t.Errorf("Valid: %s", err) 
  } 
  if !ok { 
    t.Error("expected true from Valid") 
  } 
  ok, err = srv.Validate(ctx, "wrong password", h) 
  if err != nil { 
    t.Errorf("Valid: %s", err) 
  } 
  if ok { 
    t.Error("expected false from Valid") 
  } 
} 

We will create a new service via the NewService method and then use it to call the Hash and Validate methods. We even test an unhappy case, where we get the password wrong and ensure that Validate returns false–otherwise, it wouldn't be very secure at all.

Constructors in Go

A constructor in other object-oriented languages is a special kind of function that creates instances of classes. It performs any initialization and takes in required arguments such as dependencies, among others. It is usually the only way to create an object in these languages, but it often has weird syntax or relies on naming conventions (such as the function name being the same as the class, for example).

Go doesn't have constructors; it's much simpler and just has functions, and since functions can return arguments, a constructor would just be a global function that returns a usable instance of a struct. The Go philosophy of simplicity drives these kinds of decisions for the language designers; rather than forcing people to have to learn about a new concept of constructing objects, developers only have to learn how functions work and they can build constructors with them.

Even if we aren't doing any special work in the construction of an object (such as initializing fields, validating dependencies, and so on), it is sometimes worth adding a construction function anyway. In our case, we do not want to bloat the API by exposing the vaultService type since we already have our Service interface type exposed and are hiding it inside a constructor is a nice way to achieve this.

Underneath the vaultService struct definition, add the NewService function:

// NewService makes a new Service. 
func NewService() Service { 
  return vaultService{} 
} 

Not only does this prevent us from needing to expose our internals, but if in the future we do need to do more work to prepare the vaultService for use, we can also do it without changing the API and, therefore, without requiring the users of our package to change anything on their end, which is a big win for API design.

Hashing and validating passwords with bcrypt

The first method we will implement in our service is Hash. It will take a password and generate a hash. The resulting hash can then be passed (along with a password) to the Validate method later, which will either confirm or deny that the password is correct.

Tip

To learn more about the correct way to store passwords in applications, check out the Coda Hale blog post on the subject at https://codahale.com/how-to-safely-store-a-password/.

The point of our service is to ensure that passwords never need to be stored in a database, since that's a security risk if anyone is ever able to get unauthorized access to the database. Instead, you can generate a one-way hash (it cannot be decoded) that can safely be stored, and when users attempt to authenticate, you can perform a check to see whether the password generates the same hash or not. If the hashes match, the passwords are the same; otherwise, they are not.

The bcrypt package provides methods that do this work for us in a secure and trustworthy way.

To service.go, add the Hash method:

func (vaultService) Hash(ctx context.Context, password
 string) (string, error) { 
  hash, err :=
    bcrypt.GenerateFromPassword([]byte(password),
    bcrypt.DefaultCost) 
  if err != nil { 
    return "", err 
  } 
  return string(hash), nil 
} 

Ensure that you import the appropriate bcrypt package (try golang.org/x/crypto/bcrypt). We are essentially wrapping the GenerateFromPassword function to generate the hash, which we then return provided no errors occurred.

Note that the receiver in the Hash method is just (vaultService); we don't capture the variable because there is no way we can store state on an empty struct.

Next up, let's add the Validate method:

func (vaultService) Validate(ctx context.Context,
  password, hash string) (bool, error) { 
  err := bcrypt.CompareHashAndPassword([]byte(hash),
    []byte(password)) 
  if err != nil { 
    return false, nil 
  } 
  return true, nil 
} 

Similar to Hash, we are calling bcrypt.CompareHashAndPassword to determine (in a secure way) whether the password is correct or not. If an error is returned, it means that something is amiss and we return false indicating that. Otherwise, we return true when the password is valid.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.194