11 - Account API Cleanup & Fixes
Tags: go, rest, authentication, tutorial
Published: 2020-11-29 17:18:40

---
title: 11 - Account API Cleanup & Fixes
published: true
description: Setting up an account server in Golang
tags: golang, rest, authentication, tutorial
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/2ddi3ncx7putjy077hf6.png
series: Full Stack Memorization App
---

Today we're going to clean up some of the naming, variables, and parameters passed to a few methods. Nothing will be terribly intellectually challenging, but we will modify a lot of files. 

If you find this boring or are uncertain about the changes made to the codebase, please check out the branch for this tutorial from the [Github repository](https://github.com/JacobSNGoodwin/memrizr)!

You might also find the video useful to make sure you don't miss any changes.
{% youtube rWVaKBJK_wA %}

I also want to thank everybody who has subscribed to me feed and actually reads! Thanks for subjecting yourself to some seriously dense material. I hope you find a useful snippet here or there! 😊

## Make Service and Repository Implementations Private

Our service and repository factories return interfaces. As an example, let's look at the `NewUserService` in `~/service/user_service.go`

```go
// UserService acts as a struct for injecting an implementation of UserRepository
// for use in service methods
type UserService struct {
	UserRepository model.UserRepository
}

// USConfig will hold repositories that will eventually be injected into this
// this service layer
type USConfig struct {
	UserRepository model.UserRepository
}

// NewUserService is a factory function for
// initializing a UserService with its repository layer dependencies
func NewUserService(c *USConfig) model.UserService {
	return &UserService{
		UserRepository: c.UserRepository,
	}
}
```

We want to force external packages to access the service by means of the factory and its config struct (as opposed to instantiating `UserService` directly). Therefore, we will make the actual service and repository structs package private.

### Update UserService 

To get started with this, let's make the `UserService` implementation package private. We do this by making the service name lowercase, i.e. `userService`.

```go
// userService acts as a struct for injecting an implementation of UserRepository
// for use in service methods
type userService struct {
	UserRepository model.UserRepository
}

// USConfig will hold repositories that will eventually be injected into this
// this service layer
type USConfig struct {
	UserRepository model.UserRepository
}

// NewUserService is a factory function for
// initializing a UserService with its repository layer dependencies
func NewUserService(c *USConfig) model.UserService {
	return &userService{
		UserRepository: c.UserRepository,
	}
}
```

Furthermore, you will need to return this lower-case `userService` inside of `NewUserService` and to update all of the receiver methods to use `userService`. 

As an example (you'll need to do this to all of the methods):

```go
// Signup reaches our to a UserRepository to sign up the user.
// UserRepository Create should handle checking for user exists conflicts
func (s *userService) Signup(ctx context.Context, u *model.User) error {
	// omitting body...
}
// ... subsequent code omitted
```

### Update TokenService

Let's do the same in `TokenService`, located in `~/service/token_service.go`.

```go
// tokenService used for injecting an implementation of TokenRepository
// for use in service methods along with keys and secrets for
// signing JWTs
type tokenService struct {
	// TokenRepository model.TokenRepository
	PrivKey       *rsa.PrivateKey
	PubKey        *rsa.PublicKey
	RefreshSecret string
}

// TSConfig will hold repositories that will eventually be injected into this
// this service layer
type TSConfig struct {
	// TokenRepository model.TokenRepository
	PrivKey       *rsa.PrivateKey
	PubKey        *rsa.PublicKey
	RefreshSecret string
}

// NewTokenService is a factory function for
// initializing a UserService with its repository layer dependencies
func NewTokenService(c *TSConfig) model.TokenService {
	return &tokenService{
		PrivKey:       c.PrivKey,
		PubKey:        c.PubKey,
		RefreshSecret: c.RefreshSecret,
	}
}
```

Also remember to update the factory and receiver methods to use `tokenService`.

### Update PGUserRepository

Let's do the same for our `PGUserRepository`.

```go
// pgUserRepository is data/repository implementation
// of service layer UserRepository
type pgUserRepository struct {
	DB *sqlx.DB
}

// NewUserRepository is a factory for initializing User Repositories
func NewUserRepository(db *sqlx.DB) model.UserRepository {
	return &pgUserRepository{
		DB: db,
	}
}
```

Again, remember to update the factory and receiver methods to use `pGUserRepository`!

## Context Fixes

I want to forward our `Context` down the call chain (handler -> service -> repository -> data sources), so that we can timeout any handler, along with calls all the way to the data sources, after a lengthy amount of time. To do this, though, we need to extract the `Request().Context` from off of the gin context.

We do this by passing the request context to service method calls inside of `handler` layer methods.

We need to update our current 2 handlers, `Signup` and `Me`. 

### Handler Updates

In `~/handler/me.go`:

```go

// ... code omitted

// use the Request Context
	ctx := c.Request.Context()
	u, err := h.UserService.Get(ctx, uid)

// ... code omitted

```

And in `~/handler/signup.go`, update the calls to `Signup` and `NewPairFromUser` methods with the `Context` from the `Request`. We'll use the same context for both calls, though I can definitely see cases where this would not be prudent.

```go
	ctx := c.Request.Context()
	err := h.UserService.Signup(ctx, u)

	// error checking here

	// create token pair as strings
	tokens, err := h.TokenService.NewPairFromUser(ctx, u, "")
```

### Fix Handler Layer Tests

Now that we're using a plain Golang context to call service methods, we need to fix our `signup_test.go` and `me_test.go`.

You merely need to replace mock call arguments using:

```go
mock.AnythingOfType("*gin.Context")
```

with the following **in both test files**.

```go
mock.AnythingOfType("*context.emptyCtx")
```

We use `*context.emptyCtx` here, which is an alias for `context.Background()`, a non-nil, empty context (we don't have any context fields in the unit tests).

### SQLX

I also want to use methods in SQLX that can receive the context. To do this, we'll update method calls on `DB` in the `PGUserRepository` to use "Context" versions of each method. As an example, let's look at the `Create` method and change `r.DB.Get` to `r.DB.GetContext`, remembering to pass in the context.

```go
// Create reaches out to database SQLX api
func (r *pgUserRepository) Create(ctx context.Context, u *model.User) error {
	query := "INSERT INTO users (email, password) VALUES ($1, $2) RETURNING *"

  // update this method to "GetContext"
	if err := r.DB.GetContext(ctx, u, query, u.Email, u.Password); err != nil {
		// ... code omitted
	}
	// ... code omitted
}
```

Also make sure to update the `Get` call in `FindByID` to `GetContext`.

## Update Environment Variables Usage

To make our app more "configurable," I want to make the following updates: 

1. Pass the `ACCOUNT_API_URL` environment variable to the handler config. 
2. Set the expiration duration for both JWTs as environment variables so a user can easily set them per their requirements or per environment.

### Pass ACCOUNT_API_URL to Handler Config

Where possible, environment variables should be read in from the `main` package so the app can terminate early if there is any configuration error. 

Let's add `BaseURL` as a field to our `Config` inside of `~/handler/handler.go`. This way we can instantiate the handler with this BaseURL inside of the main package.

```go
// Config will hold services that will eventually be injected into this
// handler layer on handler initialization
type Config struct {
	R            *gin.Engine
	UserService  model.UserService
	TokenService model.TokenService
	BaseURL      string
}
```

Then inside of the `NewHandler` function, we'll use this value to group our handlers.

```go
// Create a group, or base url for all routes
	g := c.R.Group(c.BaseURL)
```

Let's read and pass this environment variable to `NewHandler` inside of `injection.go`. 

```go
// read in ACCOUNT_API_URL
	baseURL := os.Getenv("ACCOUNT_API_URL")

	handler.NewHandler(&handler.Config{
		R:            router,
		UserService:  userService,
		TokenService: tokenService,
		BaseURL:      baseURL,
	})
```

We have also instantiated our handler inside of unit tests, but we do not need to make any changes as the tests work with an empty string for the `BaseURL`. Omitted `string` fields in a struct will default to an empty string in Go. 

### JWT Expiration

We currently set the JWT expiration with hard-coded values inside of the token utility methods found in `~/service/tokens.go`. I want to make these functions configurable. Let's add environment variables for expiration time in seconds in `.env.dev`.

```toml
ID_TOKEN_EXP=900 #15 mins in seconds
REFRESH_TOKEN_EXP=259200 #3 days in seconds
```

We'll read in and parse these environment variables the injection file in `package main`, as we did with the BaseURL. But first, we need to add these timeouts to our `tokenService`, `TSConfig`, and `NewTokenService` in `~/service/token_service.go`. We'll add them as `int64` type.

```go
// tokenService used for injecting an implementation of TokenRepository
// for use in service methods along with keys and secrets for
// signing JWTs
type tokenService struct {
	// TokenRepository model.TokenRepository
	PrivKey               *rsa.PrivateKey
	PubKey                *rsa.PublicKey
	RefreshSecret         string
	IDExpirationSecs      int64
	RefreshExpirationSecs int64
}
// TSConfig will hold repositories that will eventually be injected into this
// this service layer
type TSConfig struct {
	// TokenRepository model.TokenRepository
	PrivKey               *rsa.PrivateKey
	PubKey                *rsa.PublicKey
	RefreshSecret         string
	IDExpirationSecs      int64
	RefreshExpirationSecs int64
}

// NewTokenService is a factory function for
// initializing a UserService with its repository layer dependencies
func NewTokenService(c *TSConfig) model.TokenService {
	return &tokenService{
		PrivKey:               c.PrivKey,
		PubKey:                c.PubKey,
		RefreshSecret:         c.RefreshSecret,
		IDExpirationSecs:      c.IDExpirationSecs,
		RefreshExpirationSecs: c.RefreshExpirationSecs,
	}
}
```

Then inside of `injection.go`, we'll parse the environment variables and call `NewTokenService` with them:

```go
// load expiration lengths from env variables and parse as int
	idTokenExp := os.Getenv("ID_TOKEN_EXP")
	refreshTokenExp := os.Getenv("REFRESH_TOKEN_EXP")

	idExp, err := strconv.ParseInt(idTokenExp, 0, 64)
	if err != nil {
		return nil, fmt.Errorf("could not parse ID_TOKEN_EXP as int: %w", err)
	}

	refreshExp, err := strconv.ParseInt(refreshTokenExp, 0, 64)
	if err != nil {
		return nil, fmt.Errorf("could not parse REFRESH_TOKEN_EXP as int: %w", err)
	}

	tokenService := service.NewTokenService(&service.TSConfig{
		PrivKey:               privKey,
		PubKey:                pubKey,
		RefreshSecret:         refreshSecret,
		IDExpirationSecs:      idExp,
		RefreshExpirationSecs: refreshExp,
	})
```

We use `int64` as this is the type of int used golang's `time` package.

The final thing we need to do is accept these values in our utility functions, found in `~/service/token.go`, for generating tokens.

Note that in `generateRefreshToken` we are working with `time` and `time.Duration` so we have to do some casting of our `int64`. 

```go
// generateIDToken generates an IDToken which is a jwt with myCustomClaims
// Could call this GenerateIDTokenString, but the signature makes this fairly clear
func generateIDToken(u *model.User, key *rsa.PrivateKey, exp int64) (string, error) {
	unixTime := time.Now().Unix()
  tokenExp := unixTime + exp
  // ... code omitted
}

// generateRefreshToken creates a refresh token
// The refresh token stores only the user's ID, a string
func generateRefreshToken(uid uuid.UUID, key string, exp int64) (*RefreshToken, error) {
	currentTime := time.Now()
	tokenExp := currentTime.Add(time.Duration(exp) * time.Second)
	tokenID, err := uuid.NewRandom() // v4 uuid in the google uuid lib
```

Let's now make sure to call these functions inside of `token_service.go` by passing in the expiration times!

```go
// ... code omitted

  idToken, err := generateIDToken(u, s.PrivKey, s.IDExpirationSecs)
  
  // not showing code here

  refreshToken, err := generateRefreshToken(u.UID, s.RefreshSecret, s.RefreshExpirationSecs)

```

### Update Token Service Test

Let's update our tests in `~/service/token_service_test.go` to take in these new expiration parameters. We'll hard code the expiration durations in seconds ad the start of the test.

```go
func TestNewPairFromUser(t *testing.T) {
	var idExp int64 = 15 * 60
	var refreshExp int64 = 3 * 24 * 2600

	priv, _ := ioutil.ReadFile("../rsa_private_test.pem")
	privKey, _ := jwt.ParseRSAPrivateKeyFromPEM(priv)
	pub, _ := ioutil.ReadFile("../rsa_public_test.pem")
	pubKey, _ := jwt.ParseRSAPublicKeyFromPEM(pub)
	secret := "anotsorandomtestsecret"

	// ... code omitted
}
```
We also need to update the expectations in this test to use the expiration values passed to `NewTokenService`. We do this by updating the `expectedExpiresAt...` statement for each token. 

```go
    // for idToken
    expiresAt := time.Unix(idTokenClaims.StandardClaims.ExpiresAt, 0)
		expectedExpiresAt := time.Now().Add(time.Duration(idExp) * time.Second)
    assert.WithinDuration(t, expectedExpiresAt, expiresAt, 5*time.Second)
    
    // for refreshToken
    expiresAt = time.Unix(refreshTokenClaims.StandardClaims.ExpiresAt, 0)
		expectedExpiresAt = time.Now().Add(time.Duration(refreshExp) * time.Second)
		assert.WithinDuration(t, expectedExpiresAt, expiresAt, 5*time.Second)
```

## Accept Only JSON and multipart/form

Our struct tags for our `model.User` and `model.TokenPair` only support JSON. Currently, when we bind data with our `bindData` function in `~/handler/bind_data.go`, we might receive form or XML data in the HTTP body. I want to make sure to send a clear error if the user sends anything other than application/json.

Let's add some code in `~/handler/bind_data` at the top of the `bindData` function.

```go
// send error if Content-Type != application/json
	if c.ContentType() != "application/json" {
		msg := fmt.Sprintf("%s only accepts Content-Type application/json", c.FullPath())

		err := apperrors.NewUnsupportedMediaType(msg)

		c.JSON(err.Status(), gin.H{
			"error": err,
		})
		return false
	}
```

Note that I added a new custom error type called `UnsupportedMediaType`.  You can see this added in our `~/model/apperrors/apperrors.go`.

```go
// "Set" of valid errorTypes
const (
	Authorization        Type = "AUTHORIZATION"        // Authentication Failures -
	BadRequest           Type = "BADREQUEST"           // Validation errors / BadInput
	Conflict             Type = "CONFLICT"             // Already exists (eg, create account with existent email) - 409
	Internal             Type = "INTERNAL"             // Server (500) and fallback errors
	NotFound             Type = "NOTFOUND"             // For not finding resource
	PayloadTooLarge      Type = "PAYLOADTOOLARGE"      // for uploading tons of JSON, or an image over the limit - 413
	UnsupportedMediaType Type = "UNSUPPORTEDMEDIATYPE" // for http 415
)

// ... code omitted

// Status is a mapping errors to status codes
// Of course, this is somewhat redundant since
// our errors already map http status codes
func (e *Error) Status() int {
	switch e.Type {
	case Authorization:
		return http.StatusUnauthorized
	case BadRequest:
		return http.StatusBadRequest
	case Conflict:
		return http.StatusConflict
	case Internal:
		return http.StatusInternalServerError
	case NotFound:
		return http.StatusNotFound
	case PayloadTooLarge:
		return http.StatusRequestEntityTooLarge
	case UnsupportedMediaType:
		return http.StatusUnsupportedMediaType
	default:
		return http.StatusInternalServerError
	}
}

// ... code omitted

// NewUnsupportedMediaType to create an error for 415
func NewUnsupportedMediaType(reason string) *Error {
	return &Error{
		Type:    UnsupportedMediaType,
		Message: reason,
	}
}
```

## Run Server

Having made the above fixes, you should now be able to run the application. If you would like to see examples of sending HTTP requests (including bodies in formats other than application/json), please check out the video!

`docker-compose up`

## Run All Unit Tests

Let's change into the account folder, and make sure all of our tests pass.

`cd account`

`go test -v ./...`

## Conclusion

I hope you're now in sync with me! Again, you can also go ahead and checkout the code for the [`lesson-11` branch on Github](https://github.com/JacobSNGoodwin/memrizr/tree/lesson-11) to have these updates.

Next time we'll get working on creating a `TokenRepository` for storing valid refresh tokens in Redis.

Hasta entonces, chau!