Repository: google/exposure-notifications-server
Pull request #502 (2020-05-31 02:20:13)

apply gofmt

google-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/502#" title="Author self-approved">mikehelmick</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)~~ [mikehelmick]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/internal/admin/authorizedapps/save.go b/internal/admin/authorizedapps/save.go
index a548906..d9f57bd 100644
--- a/internal/admin/authorizedapps/save.go
+++ b/internal/admin/authorizedapps/save.go
@@ -32,7 +32,7 @@ type saveController struct {
 	env    *serverenv.ServerEnv
 }
 
-func NewSave(c *admin.Config, env *serverenv.ServerEnv) *saveController {
+func NewSave(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &saveController{config: c, env: env}
 }
 
@@ -90,7 +90,6 @@ func (h *saveController) Execute(c *gin.Context) {
 		m["previousKey"] = base64.StdEncoding.EncodeToString([]byte(authApp.AppPackageName))
 		c.HTML(http.StatusOK, "authorizedapp", m)
 		return
-
 	} else if form.Action == "delete" {
 		priorKey := form.PriorKey()
 
diff --git a/internal/admin/authorizedapps/view.go b/internal/admin/authorizedapps/view.go
index f98cbe4..af7f392 100644
--- a/internal/admin/authorizedapps/view.go
+++ b/internal/admin/authorizedapps/view.go
@@ -31,7 +31,7 @@ type viewController struct {
 	env    *serverenv.ServerEnv
 }
 
-func NewView(c *admin.Config, env *serverenv.ServerEnv) *viewController {
+func NewView(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &viewController{config: c, env: env}
 }
 
diff --git a/internal/admin/config.go b/internal/admin/config.go
index 2438f74..0b81ec5 100644
--- a/internal/admin/config.go
+++ b/internal/admin/config.go
@@ -72,14 +72,14 @@ func (c *Config) RenderTemplate(w http.ResponseWriter, tmpl string, p TemplateMa
 	t, err := template.ParseFiles(files...)
 	if err != nil {
 		w.WriteHeader(http.StatusInternalServerError)
-		w.Write([]byte(err.Error()))
+		fmt.Fprint(w, err)
 		log.Printf("ERROR: %v", err)
 		return err
 	}
 	if err := t.ExecuteTemplate(w, tmpl, p); err != nil {
 		message := fmt.Sprintf("error rendering template: %v", err)
 		w.WriteHeader(http.StatusInternalServerError)
-		w.Write([]byte(message))
+		fmt.Fprint(w, message)
 		log.Printf("ERROR: %v", err)
 		return fmt.Errorf("error rendering template: %w", err)
 	}
diff --git a/internal/admin/controller.go b/internal/admin/controller.go
index fb77392..0d26a6e 100644
--- a/internal/admin/controller.go
+++ b/internal/admin/controller.go
@@ -6,8 +6,8 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-// Controller is the interfactor for controlers that can be pluggied into
-// Gin for the admin console portion of this project.
+// Controller is the interfactor for controllers that can be pluggied into Gin
+// for the admin console portion of this project.
 type Controller interface {
 	Execute(g *gin.Context)
 }
diff --git a/internal/admin/exports/save.go b/internal/admin/exports/save.go
index 7349a2e..4e6053d 100644
--- a/internal/admin/exports/save.go
+++ b/internal/admin/exports/save.go
@@ -31,7 +31,7 @@ type saveController struct {
 	env    *serverenv.ServerEnv
 }
 
-func NewSave(c *admin.Config, env *serverenv.ServerEnv) *saveController {
+func NewSave(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &saveController{config: c, env: env}
 }
 
diff --git a/internal/admin/exports/view.go b/internal/admin/exports/view.go
index b1a680f..9f2c3e8 100644
--- a/internal/admin/exports/view.go
+++ b/internal/admin/exports/view.go
@@ -32,7 +32,7 @@ type viewController struct {
 	env    *serverenv.ServerEnv
 }
 
-func NewView(c *admin.Config, env *serverenv.ServerEnv) *viewController {
+func NewView(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &viewController{config: c, env: env}
 }
 
diff --git a/internal/admin/index/index.go b/internal/admin/index/index.go
index 0fd77fb..1815737 100644
--- a/internal/admin/index/index.go
+++ b/internal/admin/index/index.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package index contains admin console handler for the main landing page.
+// Package index contains admin console indexHandler for the main landing page.
 package index
 
 import (
@@ -30,7 +30,7 @@ type indexHandler struct {
 	env    *serverenv.ServerEnv
 }
 
-func New(c *admin.Config, env *serverenv.ServerEnv) *indexHandler {
+func New(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &indexHandler{config: c, env: env}
 }
 
diff --git a/internal/admin/siginfo/save.go b/internal/admin/siginfo/save.go
index 0c2c654..d968a39 100644
--- a/internal/admin/siginfo/save.go
+++ b/internal/admin/siginfo/save.go
@@ -31,7 +31,7 @@ type saveController struct {
 	env    *serverenv.ServerEnv
 }
 
-func NewSave(c *admin.Config, env *serverenv.ServerEnv) *saveController {
+func NewSave(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &saveController{config: c, env: env}
 }
 
diff --git a/internal/admin/siginfo/view.go b/internal/admin/siginfo/view.go
index 9dd029d..4a05d27 100644
--- a/internal/admin/siginfo/view.go
+++ b/internal/admin/siginfo/view.go
@@ -30,7 +30,7 @@ type viewController struct {
 	env    *serverenv.ServerEnv
 }
 
-func NewView(c *admin.Config, env *serverenv.ServerEnv) *viewController {
+func NewView(c *admin.Config, env *serverenv.ServerEnv) admin.Controller {
 	return &viewController{config: c, env: env}
 }
 
diff --git a/internal/android/safetynet.go b/internal/android/safetynet.go
index 26d9505..ab7f78c 100644
--- a/internal/android/safetynet.go
+++ b/internal/android/safetynet.go
@@ -31,7 +31,7 @@ import (
 	"github.com/dgrijalva/jwt-go"
 )
 
-// The VerifyOpts determine the fields that are required for verification
+// The VerifyOpts determine the fields that are required for verification.
 type VerifyOpts struct {
 	AppPkgName      string
 	APKDigest       []string
@@ -165,7 +165,7 @@ func VerifyOptsFor(c *model.AuthorizedApp, from time.Time, nonce string) *Verify
 
 // The keyFunc is based on the Android sample code
 // https://github.com/googlesamples/android-play-safetynet/blob/d7513a54e2f28c0dcd7f8d8d0fa03adb5d87b91a/server/java/src/main/java/OfflineVerify.java
-func keyFunc(ctx context.Context, tok *jwt.Token) (interface{}, error) {
+func keyFunc(tok *jwt.Token) (interface{}, error) {
 	x5c, ok := tok.Header["x5c"].([]interface{})
 	if !ok || len(x5c) == 0 {
 		return nil, fmt.Errorf("attestation is missing certificate")
@@ -215,11 +215,7 @@ func verifyAttestation(ctx context.Context, signedAttestation string) (jwt.MapCl
 	defer trace.StartRegion(ctx, "verifyAttestation").End()
 	// jwt.Parse also validates the signature after extracting
 	// the key via the keyFunc, which validates the certificate chain.
-	token, err := jwt.Parse(signedAttestation,
-		func(tok *jwt.Token) (interface{}, error) {
-			return keyFunc(ctx, tok)
-		})
-
+	token, err := jwt.Parse(signedAttestation, keyFunc)
 	if err != nil {
 		return nil, fmt.Errorf("jwt.Parse: %w", err)
 	}
diff --git a/internal/authorizedapp/database/authorized_app.go b/internal/authorizedapp/database/authorized_app.go
index e52e896..f43ab86 100644
--- a/internal/authorizedapp/database/authorized_app.go
+++ b/internal/authorizedapp/database/authorized_app.go
@@ -17,6 +17,7 @@ package database
 import (
 	"context"
 	"database/sql"
+	"errors"
 	"fmt"
 	"strings"
 	"time"
@@ -47,12 +48,12 @@ func (aa *AuthorizedAppDB) InsertAuthorizedApp(ctx context.Context, m *model.Aut
 		result, err := tx.Exec(ctx, `
 			INSERT INTO
 				AuthorizedApp
-			  (app_package_name, platform, allowed_regions, allowed_health_authority_ids,
+			  (app_package_name, platform, allowed_regions, allowed_health_authority_ids, bypass_health_authority_verification,
 			   safetynet_disabled, safetynet_apk_digest, safetynet_cts_profile_match, safetynet_basic_integrity, safetynet_past_seconds, safetynet_future_seconds,
 			   devicecheck_disabled, devicecheck_team_id, devicecheck_key_id, devicecheck_private_key_secret)
 			VALUES
-			  ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
-		`, m.AppPackageName, m.Platform, m.AllAllowedRegions(), m.AllAllowedHealthAuthorityIDs(),
+			  ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
+		`, m.AppPackageName, m.Platform, m.AllAllowedRegions(), m.AllAllowedHealthAuthorityIDs(), m.BypassHealthAuthorityVerification,
 			m.SafetyNetDisabled, m.SafetyNetApkDigestSHA256, m.SafetyNetCTSProfileMatch, m.SafetyNetBasicIntegrity, int64(m.SafetyNetPastTime.Seconds()), int64(m.SafetyNetFutureTime.Seconds()),
 			m.DeviceCheckDisabled, m.DeviceCheckTeamID, m.DeviceCheckKeyID, m.DeviceCheckPrivateKeySecret)
 
@@ -71,12 +72,12 @@ func (aa *AuthorizedAppDB) UpdateAuthorizedApp(ctx context.Context, priorKey str
 		result, err := tx.Exec(ctx, `
 			UPDATE AuthorizedApp
 			SET
-				app_package_name = $1, platform = $2, allowed_regions = $3, allowed_health_authority_ids = $4,
-				safetynet_disabled = $5, safetynet_apk_digest = $6, safetynet_cts_profile_match = $7, safetynet_basic_integrity = $8, safetynet_past_seconds = $9, safetynet_future_seconds = $10,
-				devicecheck_disabled = $11, devicecheck_team_id = $12, devicecheck_key_id = $13, devicecheck_private_key_secret = $14
+				app_package_name = $1, platform = $2, allowed_regions = $3, allowed_health_authority_ids = $4, bypass_health_authority_verification = $5,
+				safetynet_disabled = $6, safetynet_apk_digest = $7, safetynet_cts_profile_match = $8, safetynet_basic_integrity = $9, safetynet_past_seconds = $10, safetynet_future_seconds = $11,
+				devicecheck_disabled = $12, devicecheck_team_id = $13, devicecheck_key_id = $14, devicecheck_private_key_secret = $15
 			WHERE
-				app_package_name = $15
-			`, m.AppPackageName, m.Platform, m.AllAllowedRegions(), m.AllAllowedHealthAuthorityIDs(),
+				app_package_name = $16
+			`, m.AppPackageName, m.Platform, m.AllAllowedRegions(), m.AllAllowedHealthAuthorityIDs(), m.BypassHealthAuthorityVerification,
 			m.SafetyNetDisabled, m.SafetyNetApkDigestSHA256, m.SafetyNetCTSProfileMatch, m.SafetyNetBasicIntegrity, int64(m.SafetyNetPastTime.Seconds()), int64(m.SafetyNetFutureTime.Seconds()),
 			m.DeviceCheckDisabled, m.DeviceCheckTeamID, m.DeviceCheckKeyID, m.DeviceCheckPrivateKeySecret, priorKey)
 		if err != nil {
@@ -122,7 +123,7 @@ func (aa *AuthorizedAppDB) GetAllAuthorizedApps(ctx context.Context, sm secrets.
 
 	query := `
 		SELECT
-			app_package_name, platform, allowed_regions, allowed_health_authority_ids,
+			app_package_name, platform, allowed_regions, allowed_health_authority_ids, bypass_health_authority_verification,
 			safetynet_disabled, safetynet_apk_digest, safetynet_cts_profile_match, safetynet_basic_integrity, safetynet_past_seconds, safetynet_future_seconds,
 			devicecheck_disabled, devicecheck_team_id, devicecheck_key_id, devicecheck_private_key_secret
 		FROM
@@ -152,8 +153,8 @@ func (aa *AuthorizedAppDB) GetAllAuthorizedApps(ctx context.Context, sm secrets.
 
 // GetAuthorizedApp loads a single AuthorizedApp for the given name. If no row
 // exists, this returns nil.
-func (db *AuthorizedAppDB) GetAuthorizedApp(ctx context.Context, sm secrets.SecretManager, name string) (*model.AuthorizedApp, error) {
-	conn, err := db.db.Pool.Acquire(ctx)
+func (aa *AuthorizedAppDB) GetAuthorizedApp(ctx context.Context, sm secrets.SecretManager, name string) (*model.AuthorizedApp, error) {
+	conn, err := aa.db.Pool.Acquire(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("acquiring connection: %v", err)
 	}
@@ -161,7 +162,7 @@ func (db *AuthorizedAppDB) GetAuthorizedApp(ctx context.Context, sm secrets.Secr
 
 	query := `
 		SELECT
-			app_package_name, platform, allowed_regions, allowed_health_authority_ids,
+			app_package_name, platform, allowed_regions, allowed_health_authority_ids, bypass_health_authority_verification,
 			safetynet_disabled, safetynet_apk_digest, safetynet_cts_profile_match, safetynet_basic_integrity, safetynet_past_seconds, safetynet_future_seconds,
 			devicecheck_disabled, devicecheck_team_id, devicecheck_key_id, devicecheck_private_key_secret
 		FROM
@@ -180,11 +181,11 @@ func scanOneAuthorizedApp(ctx context.Context, row pgx.Row, sm secrets.SecretMan
 	var safetyNetPastSeconds, safetyNetFutureSeconds *int
 	var deviceCheckTeamID, deviceCheckKeyID, deviceCheckPrivateKeySecret sql.NullString
 	if err := row.Scan(
-		&config.AppPackageName, &config.Platform, &allowedRegions, &AllowedHealthAuthorityIDs,
+		&config.AppPackageName, &config.Platform, &allowedRegions, &AllowedHealthAuthorityIDs, &config.BypassHealthAuthorityVerification,
 		&config.SafetyNetDisabled, &config.SafetyNetApkDigestSHA256, &config.SafetyNetCTSProfileMatch, &config.SafetyNetBasicIntegrity, &safetyNetPastSeconds, &safetyNetFutureSeconds,
 		&config.DeviceCheckDisabled, &deviceCheckTeamID, &deviceCheckKeyID, &deviceCheckPrivateKeySecret,
 	); err != nil {
-		if err == pgx.ErrNoRows {
+		if errors.Is(err, pgx.ErrNoRows) {
 			return nil, nil
 		}
 		return nil, err
diff --git a/internal/authorizedapp/model/authorized_app_model.go b/internal/authorizedapp/model/authorized_app_model.go
index 58b87d2..69f4c7c 100644
--- a/internal/authorizedapp/model/authorized_app_model.go
+++ b/internal/authorizedapp/model/authorized_app_model.go
@@ -43,7 +43,8 @@ type AuthorizedApp struct {
 
 	// AllowedHealthAuthorityIDs represents the set of allowed health authorities
 	// that this app can obtain and verify diagnosis verification certificates from.
-	AllowedHealthAuthorityIDs map[int64]struct{}
+	AllowedHealthAuthorityIDs         map[int64]struct{}
+	BypassHealthAuthorityVerification bool
 
 	// DEPRECATION NOTICE:
 	// Everything below here is deprecated.
@@ -116,12 +117,12 @@ func (c *AuthorizedApp) Validate() []string {
 	return errors
 }
 
-// IsIOS returns true if the platform is equal to `iosDevice`
+// IsIOS returns true if the platform is equal to `iosDevice`.
 func (c *AuthorizedApp) IsIOS() bool {
 	return c.Platform == IosDevice
 }
 
-// IsAndroid returns true if the platform is equal to `android`
+// IsAndroid returns true if the platform is equal to `android`.
 func (c *AuthorizedApp) IsAndroid() bool {
 	return c.Platform == AndroidDevice
 }
diff --git a/internal/database/connection.go b/internal/database/connection.go
index 9939ba0..f21811f 100644
--- a/internal/database/connection.go
+++ b/internal/database/connection.go
@@ -39,11 +39,7 @@ func NewFromEnv(ctx context.Context, config *Config) (*DB, error) {
 	logger := logging.FromContext(ctx)
 	logger.Infof("Creating connection pool.")
 
-	connStr, err := dbConnectionString(ctx, config)
-
-	if err != nil {
-		return nil, fmt.Errorf("invalid database config: %v", err)
-	}
+	connStr := dbConnectionString(config)
 
 	pool, err := pgxpool.Connect(ctx, connStr)
 	if err != nil {
@@ -62,13 +58,13 @@ func (db *DB) Close(ctx context.Context) {
 
 // dbConnectionString builds a connection string suitable for the pgx Postgres driver, using the
 // values of vars.
-func dbConnectionString(ctx context.Context, config *Config) (string, error) {
+func dbConnectionString(config *Config) string {
 	vals := dbValues(config)
 	var p []string
 	for k, v := range vals {
 		p = append(p, fmt.Sprintf("%s=%s", k, v))
 	}
-	return strings.Join(p, " "), nil
+	return strings.Join(p, " ")
 }
 
 // dbURI builds a Postgres URI suitable for the lib/pq driver, which is used by
diff --git a/internal/database/database_util.go b/internal/database/database_util.go
index 6c38b28..c212643 100644
--- a/internal/database/database_util.go
+++ b/internal/database/database_util.go
@@ -16,6 +16,7 @@ package database
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net"
 	"net/url"
@@ -137,7 +138,7 @@ func dbMigrate(u string) error {
 	if err != nil {
 		return fmt.Errorf("failed create migrate: %w", err)
 	}
-	if err := m.Up(); err != nil && err != migrate.ErrNoChange {
+	if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
 		return fmt.Errorf("failed run migrate: %w", err)
 	}
 	srcErr, dbErr := m.Close()
diff --git a/internal/export/batcher.go b/internal/export/batcher.go
index 8cf3588..dc8ae75 100644
--- a/internal/export/batcher.go
+++ b/internal/export/batcher.go
@@ -46,14 +46,18 @@ func (s *Server) CreateBatchesHandler(w http.ResponseWriter, r *http.Request) {
 			metrics.WriteInt("export-batcher-lock-contention", true, 1)
 			msg := fmt.Sprintf("Lock %s already in use, no work will be performed", lock)
 			logger.Infof(msg)
-			w.Write([]byte(msg)) // We return status 200 here so that Cloud Scheduler does not retry.
+			fmt.Fprint(w, msg) // We return status 200 here so that Cloud Scheduler does not retry.
 			return
 		}
 		logger.Errorf("Could not acquire lock %s: %v", lock, err)
 		http.Error(w, fmt.Sprintf("Could not acquire lock %s, check logs.", lock), http.StatusInternalServerError)
 		return
 	}
-	defer unlockFn()
+	defer func() {
+		if err := unlockFn(); err != nil {
+			logger.Errorf("failed to unlock: %v", err)
+		}
+	}()
 
 	totalConfigs := 0
 	totalBatches := 0
@@ -141,7 +145,6 @@ type batchRange struct {
 var sanityDate = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
 
 func makeBatchRanges(period time.Duration, latestEnd, now time.Time, truncateWindow time.Duration) []batchRange {
-
 	// Compute the end of the exposure publish window; we don't want any batches with an end date greater than this time.
 	publishEnd := publishmodel.TruncateWindow(now, truncateWindow)
 
diff --git a/internal/export/config.go b/internal/export/config.go
index cf68b7e..4def66e 100644
--- a/internal/export/config.go
+++ b/internal/export/config.go
@@ -24,7 +24,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/storage"
 )
 
-// Compile-time check to assert this config matches requirements
+// Compile-time check to assert this config matches requirements.
 var _ setup.BlobstoreConfigProvider = (*Config)(nil)
 var _ setup.DatabaseConfigProvider = (*Config)(nil)
 var _ setup.KeyManagerConfigProvider = (*Config)(nil)
diff --git a/internal/export/database/export.go b/internal/export/database/export.go
index 7525289..50e690e 100644
--- a/internal/export/database/export.go
+++ b/internal/export/database/export.go
@@ -267,7 +267,7 @@ func (db *ExportDB) ListAllSigntureInfos(ctx context.Context) ([]*model.Signatur
 		if rows.Err() != nil {
 			return nil, rows.Err()
 		}
-		si, err := scanOneSignatureInfo(ctx, rows)
+		si, err := scanOneSignatureInfo(rows)
 		if err != nil {
 			return nil, err
 		}
@@ -301,7 +301,7 @@ func (db *ExportDB) LookupSignatureInfos(ctx context.Context, ids []int64, valid
 		if rows.Err() != nil {
 			return nil, rows.Err()
 		}
-		si, err := scanOneSignatureInfo(ctx, rows)
+		si, err := scanOneSignatureInfo(rows)
 		if err != nil {
 			return nil, err
 		}
@@ -328,10 +328,10 @@ func (db *ExportDB) GetSignatureInfo(ctx context.Context, id int64) (*model.Sign
 			id = $1
 		`, id)
 
-	return scanOneSignatureInfo(ctx, row)
+	return scanOneSignatureInfo(row)
 }
 
-func scanOneSignatureInfo(ctx context.Context, row pgx.Row) (*model.SignatureInfo, error) {
+func scanOneSignatureInfo(row pgx.Row) (*model.SignatureInfo, error) {
 	var info model.SignatureInfo
 	var thru *time.Time
 	if err := row.Scan(&info.ID, &info.SigningKey, &info.AppPackageName, &info.BundleID, &info.SigningKeyVersion, &info.SigningKeyID, &thru); err != nil {
@@ -346,7 +346,6 @@ func scanOneSignatureInfo(ctx context.Context, row pgx.Row) (*model.SignatureInf
 // LatestExportBatchEnd returns the end time of the most recent ExportBatch for
 // a given ExportConfig. It returns the zero time if no previous ExportBatch
 // exists.
-// TODO(squee1945): This needs a
 func (db *ExportDB) LatestExportBatchEnd(ctx context.Context, ec *model.ExportConfig) (time.Time, error) {
 	conn, err := db.db.Pool.Acquire(ctx)
 	if err != nil {
@@ -461,7 +460,6 @@ func (db *ExportDB) LeaseBatch(ctx context.Context, ttl time.Duration, now time.
 		// In a serialized transaction, fetch the existing batch and make sure it can be leased, then lease it.
 		leased := false
 		err := db.db.InTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
-
 			row := tx.QueryRow(ctx, `
 				SELECT
 					status, lease_expires
@@ -708,7 +706,6 @@ func (db *ExportDB) DeleteFilesBefore(ctx context.Context, before time.Time, blo
 	batchFileDeleteCounter := make(map[int64]int)
 
 	for _, f := range files {
-
 		// If file is already deleted, skip to the next.
 		if f.fileStatus == model.ExportBatchDeleted {
 			batchFileDeleteCounter[f.batchID]++
@@ -724,7 +721,7 @@ func (db *ExportDB) DeleteFilesBefore(ctx context.Context, before time.Time, blo
 
 		err := db.db.InTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 			// Update Status in ExportFile.
-			if err := updateExportFileStatus(ctx, tx, f.batchID, f.filename, model.ExportBatchDeleted); err != nil {
+			if err := updateExportFileStatus(ctx, tx, f.filename, model.ExportBatchDeleted); err != nil {
 				return fmt.Errorf("updating ExportFile: %w", err)
 			}
 
@@ -767,7 +764,7 @@ func addExportFile(ctx context.Context, tx pgx.Tx, ef *model.ExportFile) error {
 	return nil
 }
 
-func updateExportFileStatus(ctx context.Context, tx pgx.Tx, batchID int64, filename, status string) error {
+func updateExportFileStatus(ctx context.Context, tx pgx.Tx, filename, status string) error {
 	_, err := tx.Exec(ctx, `
 		UPDATE
 			ExportFile
diff --git a/internal/export/exportfile.go b/internal/export/exportfile.go
index d4ab48c..9ecef53 100644
--- a/internal/export/exportfile.go
+++ b/internal/export/exportfile.go
@@ -45,13 +45,13 @@ var (
 	fixedHeaderWidth = 16
 )
 
-type ExportSigners struct {
+type Signer struct {
 	SignatureInfo *model.SignatureInfo
 	Signer        crypto.Signer
 }
 
 // MarshalExportFile converts the inputs into an encoded byte array.
-func MarshalExportFile(eb *model.ExportBatch, exposures []*publishmodel.Exposure, batchNum, batchSize int, signers []ExportSigners) ([]byte, error) {
+func MarshalExportFile(eb *model.ExportBatch, exposures []*publishmodel.Exposure, batchNum, batchSize int, signers []*Signer) ([]byte, error) {
 	// create main exposure key export binary
 	expContents, err := marshalContents(eb, exposures, int32(batchNum), int32(batchSize), signers)
 	if err != nil {
@@ -59,7 +59,7 @@ func MarshalExportFile(eb *model.ExportBatch, exposures []*publishmodel.Exposure
 	}
 
 	// create signature file
-	sigContents, err := marshalSignature(eb, expContents, int32(batchNum), int32(batchSize), signers)
+	sigContents, err := marshalSignature(expContents, int32(batchNum), int32(batchSize), signers)
 	if err != nil {
 		return nil, fmt.Errorf("unable to marshal signature file: %w", err)
 	}
@@ -131,7 +131,7 @@ func unmarshalContent(file *zip.File) (*export.TemporaryExposureKeyExport, error
 	return message, nil
 }
 
-func marshalContents(eb *model.ExportBatch, exposures []*publishmodel.Exposure, batchNum int32, batchSize int32, signers []ExportSigners) ([]byte, error) {
+func marshalContents(eb *model.ExportBatch, exposures []*publishmodel.Exposure, batchNum int32, batchSize int32, signers []*Signer) ([]byte, error) {
 	exportBytes := fixedHeader
 	if len(exportBytes) != fixedHeaderWidth {
 		return nil, fmt.Errorf("incorrect header length: %d", len(exportBytes))
@@ -166,8 +166,8 @@ func marshalContents(eb *model.ExportBatch, exposures []*publishmodel.Exposure,
 		StartTimestamp: proto.Uint64(uint64(eb.StartTimestamp.Unix())),
 		EndTimestamp:   proto.Uint64(uint64(eb.EndTimestamp.Unix())),
 		Region:         proto.String(eb.OutputRegion),
-		BatchNum:       proto.Int32(int32(batchNum)),
-		BatchSize:      proto.Int32(int32(batchSize)),
+		BatchNum:       proto.Int32(batchNum),
+		BatchSize:      proto.Int32(batchSize),
 		Keys:           pbeks,
 		SignatureInfos: exportSigInfos,
 	}
@@ -195,8 +195,7 @@ func createSignatureInfo(si *model.SignatureInfo) *export.SignatureInfo {
 	return sigInfo
 }
 
-func marshalSignature(eb *model.ExportBatch, exportContents []byte, batchNum, batchSize int32, signers []ExportSigners) ([]byte, error) {
-
+func marshalSignature(exportContents []byte, batchNum, batchSize int32, signers []*Signer) ([]byte, error) {
 	var signatures []*export.TEKSignature
 	for _, s := range signers {
 		sig, err := generateSignature(exportContents, s.Signer)
diff --git a/internal/export/model/export_model.go b/internal/export/model/export_model.go
index 68387e6..ba2e01c 100644
--- a/internal/export/model/export_model.go
+++ b/internal/export/model/export_model.go
@@ -66,31 +66,31 @@ func (ec *ExportConfig) Validate() error {
 	return nil
 }
 
-func (e *ExportConfig) FormattedFromTime() string {
-	return e.From.Format(time.UnixDate)
+func (ec *ExportConfig) FormattedFromTime() string {
+	return ec.From.Format(time.UnixDate)
 }
 
-func (e *ExportConfig) FormattedThruTime() string {
-	if e.Thru.IsZero() {
+func (ec *ExportConfig) FormattedThruTime() string {
+	if ec.Thru.IsZero() {
 		return ""
 	}
-	return e.Thru.Format(time.UnixDate)
+	return ec.Thru.Format(time.UnixDate)
 }
 
-func (e *ExportConfig) FromHTMLDate() string {
-	return toHTMLDate(e.From)
+func (ec *ExportConfig) FromHTMLDate() string {
+	return toHTMLDate(ec.From)
 }
 
-func (e *ExportConfig) FromHTMLTime() string {
-	return toHTMLTime(e.From)
+func (ec *ExportConfig) FromHTMLTime() string {
+	return toHTMLTime(ec.From)
 }
 
-func (e *ExportConfig) ThruHTMLDate() string {
-	return toHTMLDate(e.Thru)
+func (ec *ExportConfig) ThruHTMLDate() string {
+	return toHTMLDate(ec.Thru)
 }
 
-func (e *ExportConfig) ThruHTMLTime() string {
-	return toHTMLTime(e.Thru)
+func (ec *ExportConfig) ThruHTMLTime() string {
+	return toHTMLTime(ec.Thru)
 }
 
 type ExportBatch struct {
diff --git a/internal/export/worker.go b/internal/export/worker.go
index 93b2c23..ada4201 100644
--- a/internal/export/worker.go
+++ b/internal/export/worker.go
@@ -17,6 +17,7 @@ package export
 import (
 	"context"
 	"crypto/rand"
+	"errors"
 	"fmt"
 	"math/big"
 	"net/http"
@@ -40,7 +41,8 @@ const (
 	blobOperationTimeout = 50 * time.Second
 )
 
-// WorkerHandler is a handler to iterate the rows of ExportBatch, and creates export files.
+// WorkerHandler is a handler to iterate the rows of ExportBatch, and creates
+// export files.
 func (s *Server) WorkerHandler(w http.ResponseWriter, r *http.Request) {
 	ctx, cancel := context.WithTimeout(r.Context(), s.config.WorkerTimeout)
 	defer cancel()
@@ -56,7 +58,8 @@ func (s *Server) WorkerHandler(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
-		// Only consider batches that closed a few minutes ago to allow the publish windows to close properly.
+		// Only consider batches that closed a few minutes ago to allow the publish
+		// windows to close properly.
 		minutesAgo := time.Now().Add(-5 * time.Minute)
 
 		// Check for a batch and obtain a lease for it.
@@ -76,10 +79,10 @@ func (s *Server) WorkerHandler(w http.ResponseWriter, r *http.Request) {
 			logger.Errorf("Failed to create files for batch: %v.", err)
 			continue
 		}
-		// We re-write the index file for empty batches for self-healing so that the index
-		// file reflects the ExportFile table in database. However, if a single worker
-		// processes a number of empty batches quickly, we want to avoid writing the same
-		// file repeatedly and hitting a rate limit.
+		// We re-write the index file for empty batches for self-healing so that the
+		// index file reflects the ExportFile table in database. However, if a
+		// single worker processes a number of empty batches quickly, we want to
+		// avoid writing the same file repeatedly and hitting a rate limit.
 		emitIndexForEmptyBatch = false
 
 		fmt.Fprintf(w, "Batch %d marked completed. \n", batch.BatchID)
@@ -97,9 +100,10 @@ func (s *Server) exportBatch(ctx context.Context, eb *model.ExportBatch, emitInd
 		OnlyLocalProvenance: false, // include federated ids
 	}
 
-	// Build up groups of exposures in memory. We need to use memory so we can determine the
-	// total number of groups (which is embedded in each export file). This technique avoids
-	// SELECT COUNT which would lock the database slowing new uploads.
+	// Build up groups of exposures in memory. We need to use memory so we can
+	// determine the total number of groups (which is embedded in each export
+	// file). This technique avoids SELECT COUNT which would lock the database
+	// slowing new uploads.
 	var groups [][]*publishmodel.Exposure
 	var exposures []*publishmodel.Exposure
 
@@ -144,7 +148,8 @@ func (s *Server) exportBatch(ctx context.Context, eb *model.ExportBatch, emitInd
 			return nil
 		}
 
-		// TODO(squee1945): Uploading in parallel (to a point) probably makes better use of network.
+		// TODO(squee1945): Uploading in parallel (to a point) probably makes better
+		// use of network.
 		objectName, err := s.createFile(ctx,
 			createFileInfo{
 				exposures:      exposures,
@@ -186,13 +191,13 @@ type createFileInfo struct {
 func (s *Server) createFile(ctx context.Context, cfi createFileInfo) (string, error) {
 	logger := logging.FromContext(ctx)
 
-	var signers []ExportSigners
+	var signers []*Signer
 	for _, si := range cfi.signatureInfos {
 		signer, err := s.env.GetSignerForKey(ctx, si.SigningKey)
 		if err != nil {
 			return "", fmt.Errorf("unable to get signer for key %v: %w", si.SigningKey, err)
 		}
-		signers = append(signers, ExportSigners{SignatureInfo: si, Signer: signer})
+		signers = append(signers, &Signer{SignatureInfo: si, Signer: signer})
 	}
 
 	// Generate exposure key export file.
@@ -211,8 +216,9 @@ func (s *Server) createFile(ctx context.Context, cfi createFileInfo) (string, er
 	return objectName, nil
 }
 
-// retryingCreateIndex create the index file. The index file includes _all_ batches for an ExportConfig,
-// so multiple workers may be racing to update it. We use a lock to make them line up after one another.
+// retryingCreateIndex create the index file. The index file includes _all_
+// batches for an ExportConfig, so multiple workers may be racing to update it.
+// We use a lock to make them line up after one another.
 func (s *Server) retryingCreateIndex(ctx context.Context, eb *model.ExportBatch, objectNames []string) error {
 	logger := logging.FromContext(ctx)
 
@@ -226,7 +232,7 @@ func (s *Server) retryingCreateIndex(ctx context.Context, eb *model.ExportBatch,
 
 		unlock, err := s.db.Lock(ctx, lockID, time.Minute)
 		if err != nil {
-			if err == coredb.ErrAlreadyLocked {
+			if errors.Is(err, coredb.ErrAlreadyLocked) {
 				logger.Debugf("Lock %s is locked; sleeping %v and will try again", lockID, sleep)
 				time.Sleep(sleep)
 				continue
@@ -290,7 +296,7 @@ func exportIndexFilename(eb *model.ExportBatch) string {
 	return fmt.Sprintf("%s/index.txt", eb.FilenameRoot)
 }
 
-// randomInt is inclusive, [min:max]
+// randomInt is inclusive, [min:max].
 func randomInt(min, max int) (int, error) {
 	n, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1)))
 	if err != nil {
@@ -343,7 +349,8 @@ func ensureMinNumExposures(exposures []*publishmodel.Exposure, region string, mi
 			Regions:          []string{region},
 			IntervalNumber:   intervalNumber,
 			IntervalCount:    intervalCount,
-			// The rest of the publishmodel.Exposure fields are not used in the export file.
+			// The rest of the publishmodel.Exposure fields are not used in the export
+			// file.
 		}
 		exposures = append(exposures, ek)
 	}
diff --git a/internal/federationin/federationin.go b/internal/federationin/federationin.go
index bc21e0c..276a1ae 100644
--- a/internal/federationin/federationin.go
+++ b/internal/federationin/federationin.go
@@ -117,13 +117,17 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			metrics.WriteInt("federation-pull-lock-contention", true, 1)
 			msg := fmt.Sprintf("Lock %s already in use. No work will be performed.", lock)
 			logger.Infof(msg)
-			w.Write([]byte(msg)) // We return status 200 here so that Cloud Scheduler does not retry.
+			fmt.Fprint(w, msg) // We return status 200 here so that Cloud Scheduler does not retry.
 			return
 		}
 		internalErrorf(ctx, w, "Could not acquire lock %s for query %s: %v", lock, queryID, err)
 		return
 	}
-	defer unlockFn()
+	defer func() {
+		if err := unlockFn(); err != nil {
+			logger.Errorf("failed to unlock: %v", err)
+		}
+	}()
 
 	query, err := h.db.GetFederationInQuery(ctx, queryID)
 	if err != nil {
@@ -195,7 +199,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if timeoutContext.Err() != nil && timeoutContext.Err() == context.DeadlineExceeded {
+	if errors.Is(timeoutContext.Err(), context.DeadlineExceeded) {
 		logger.Infof("Federation puller timed out at %v before fetching entire set.", h.config.Timeout)
 	}
 }
@@ -251,7 +255,6 @@ func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies,
 		// Loop through the result set, storing in publishdb.
 		var exposures []*publishmodel.Exposure
 		for _, ctr := range response.Response {
-
 			var upperRegions []string
 			for _, region := range ctr.RegionIdentifiers {
 				upperRegions = append(upperRegions, strings.ToUpper(strings.TrimSpace(region)))
@@ -260,7 +263,6 @@ func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies,
 
 			for _, cti := range ctr.ContactTracingInfo {
 				for _, key := range cti.ExposureKeys {
-
 					if cti.TransmissionRisk < publishmodel.MinTransmissionRisk || cti.TransmissionRisk > publishmodel.MaxTransmissionRisk {
 						logger.Errorf("invalid transmission risk %v - dropping record.", cti.TransmissionRisk)
 						continue
diff --git a/internal/generate/handler.go b/internal/generate/handler.go
index 8277313..978c25a 100644
--- a/internal/generate/handler.go
+++ b/internal/generate/handler.go
@@ -91,7 +91,7 @@ func (h *generateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			message := fmt.Sprintf("Error transofmring generated exposures: %v", err)
 			span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: message})
 			w.WriteHeader(http.StatusInternalServerError)
-			w.Write([]byte(message))
+			fmt.Fprint(w, message)
 			return
 		}
 
@@ -100,11 +100,11 @@ func (h *generateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			message := fmt.Sprintf("error writing exposure record: %v", err)
 			span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: message})
 			w.WriteHeader(http.StatusInternalServerError)
-			w.Write([]byte(message))
+			fmt.Fprint(w, message)
 			return
 		}
 		logger.Infof("Generated %v exposures", len(exposures))
 	}
 	w.WriteHeader(http.StatusOK)
-	w.Write([]byte("Generated exposure keys."))
+	fmt.Fprint(w, "Generated exposure keys.")
 }
diff --git a/internal/publish/model/exposure_model.go b/internal/publish/model/exposure_model.go
index 3965a2a..fac7a66 100644
--- a/internal/publish/model/exposure_model.go
+++ b/internal/publish/model/exposure_model.go
@@ -139,7 +139,7 @@ func (p *Publish) AndroidNonce() string {
 //   is configurable per installation.
 // IntervalCount must >= `minIntervalCount` and <= `maxIntervalCount`
 //   1 - 144 inclusive.
-// transmissionRisk must be >= 0 and <= 8
+// transmissionRisk must be >= 0 and <= 8.
 type ExposureKey struct {
 	Key              string `json:"key"`
 	IntervalNumber   int32  `json:"rollingStartNumber"`
@@ -149,7 +149,7 @@ type ExposureKey struct {
 
 // ExposureKeys represents a set of ExposureKey objects as input to
 // export file generation utility.
-// Keys: Required and must have length >= 1
+// Keys: Required and must have length >= 1.
 type ExposureKeys struct {
 	Keys []ExposureKey `json:"temporaryExposureKeys"`
 }
@@ -159,7 +159,7 @@ type ExposureKeys struct {
 // Exposure struct that doesn't have public fields and an
 // internal struct that does. Separate out the database model
 // from direct access.
-// Mark records as writable/nowritable - is exposure key encrypted
+// Mark records as writable/nowritable - is exposure key encrypted.
 type Exposure struct {
 	ExposureKey      []byte    `db:"exposure_key"`
 	TransmissionRisk int       `db:"transmission_risk"`
diff --git a/internal/publish/publish.go b/internal/publish/publish.go
index be0c4cc..3b83440 100644
--- a/internal/publish/publish.go
+++ b/internal/publish/publish.go
@@ -17,6 +17,7 @@ package publish
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 	"time"
@@ -98,7 +99,7 @@ func (h *publishHandler) handleRequest(w http.ResponseWriter, r *http.Request) r
 		// Config loaded, but app with that name isn't registered. This can also
 		// happen if the app was recently registered but the cache hasn't been
 		// refreshed.
-		if err == authorizedapp.ErrAppNotFound {
+		if errors.Is(err, authorizedapp.ErrAppNotFound) {
 			message := fmt.Sprintf("unauthorized app: %v", data.AppPackageName)
 			logger.Error(message)
 			span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: message})
@@ -203,7 +204,7 @@ func (h *publishHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// Handle success. If debug enabled, write the message in the response.
 	if response.status == http.StatusOK {
 		if h.config.DebugAPIResponses {
-			w.Write([]byte(response.message))
+			fmt.Fprint(w, response.message)
 		} else {
 			w.WriteHeader(http.StatusOK)
 		}
@@ -214,7 +215,7 @@ func (h *publishHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// out the error and status.
 	if h.config.DebugAPIResponses || response.errorInProd {
 		w.WriteHeader(response.status)
-		w.Write([]byte(response.message))
+		fmt.Fprint(w, response.message)
 		return
 	}
 
diff --git a/internal/secrets/azure_keyvault.go b/internal/secrets/azure_keyvault.go
index 1307ea0..4378106 100644
--- a/internal/secrets/azure_keyvault.go
+++ b/internal/secrets/azure_keyvault.go
@@ -80,5 +80,5 @@ func (kv *AzureKeyVault) GetSecretValue(ctx context.Context, name string) (strin
 	if result.Value == nil {
 		return "", fmt.Errorf("found secret %v, but value was nil", name)
 	}
-	return string(*result.Value), nil
+	return *result.Value, nil
 }
diff --git a/internal/signing/hashicorp_vault.go b/internal/signing/hashicorp_vault.go
index f64f8ca..0871031 100644
--- a/internal/signing/hashicorp_vault.go
+++ b/internal/signing/hashicorp_vault.go
@@ -77,7 +77,7 @@ type HashiCorpVaultSigner struct {
 	publicKey crypto.PublicKey
 }
 
-// NewHashiCorpVaultSigner creates a new signing interface compatiable with
+// NewHashiCorpVaultSigner creates a new signing interface compatible with
 // HashiCorp Vault's transit backend. The key name and key version are required.
 func NewHashiCorpVaultSigner(ctx context.Context, client *vaultapi.Client, name, version string) (*HashiCorpVaultSigner, error) {
 	if client == nil {
diff --git a/internal/signing/signing.go b/internal/signing/signing.go
index ab106c4..b359d72 100644
--- a/internal/signing/signing.go
+++ b/internal/signing/signing.go
@@ -36,7 +36,7 @@ type Config struct {
 
 // KeyManager defines the interface for working with a KMS system that
 // is able to sign bytes using PKI.
-// KeyManager implementations must be able to return a crypto.Signer
+// KeyManager implementations must be able to return a crypto.Signer.
 type KeyManager interface {
 	NewSigner(ctx context.Context, keyID string) (crypto.Signer, error)
 }
diff --git a/internal/storage/google_cloud_storage.go b/internal/storage/google_cloud_storage.go
index a69319d..a7d9bbd 100644
--- a/internal/storage/google_cloud_storage.go
+++ b/internal/storage/google_cloud_storage.go
@@ -33,7 +33,7 @@ type GoogleCloudStorage struct {
 }
 
 // NewGoogleCloudStorage creates a Google Cloud Storage Client, suitable
-// for use with serverenv.ServerEnv
+// for use with serverenv.ServerEnv.
 func NewGoogleCloudStorage(ctx context.Context) (Blobstore, error) {
 	client, err := storage.NewClient(ctx)
 	if err != nil {
diff --git a/internal/util/generate.go b/internal/util/generate.go
index 0653f2d..f8ec5ff 100644
--- a/internal/util/generate.go
+++ b/internal/util/generate.go
@@ -48,7 +48,7 @@ func RandomInt(maxValue int) (int, error) {
 	return int(n.Int64()), nil
 }
 
-// RandomIntWithMin is inclusive, [min:max]
+// RandomIntWithMin is inclusive, [min:max].
 func RandomIntWithMin(min, max int) (int, error) {
 	n, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1)))
 	if err != nil {
diff --git a/internal/verification/model/health_authority.go b/internal/verification/model/health_authority.go
index 1047dd8..2899c2a 100644
--- a/internal/verification/model/health_authority.go
+++ b/internal/verification/model/health_authority.go
@@ -75,7 +75,7 @@ func (k *HealthAuthorityKey) IsValidAt(t time.Time) bool {
 }
 
 // PublicKey decodes the PublicKeyPEM text and returns the `*ecdsa.PublicKey`
-// This system only supports verifying ECDSA JWTs, `alg: ES256`
+// This system only supports verifying ECDSA JWTs, `alg: ES256`.
 func (k *HealthAuthorityKey) PublicKey() (*ecdsa.PublicKey, error) {
 	block, _ := pem.Decode([]byte(k.PublicKeyPEM))
 	if block == nil || block.Type != "PUBLIC KEY" {
diff --git a/migrations/000027_add_authoried_app_require_pha.down.sql b/migrations/000027_add_authoried_app_require_pha.down.sql
new file mode 100644
index 0000000..dd0ca7d
--- /dev/null
+++ b/migrations/000027_add_authoried_app_require_pha.down.sql
@@ -0,0 +1,20 @@
+-- Copyright 2020 Google LLC
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--      http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+BEGIN;
+
+ALTER TABLE AuthorizedApp
+  DROP COLUMN bypass_health_authority_verification;
+
+END;
diff --git a/migrations/000027_add_authoried_app_require_pha.up.sql b/migrations/000027_add_authoried_app_require_pha.up.sql
new file mode 100644
index 0000000..a355101
--- /dev/null
+++ b/migrations/000027_add_authoried_app_require_pha.up.sql
@@ -0,0 +1,20 @@
+-- Copyright 2020 Google LLC
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--      http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+BEGIN;
+
+ALTER TABLE AuthorizedApp
+  ADD COLUMN bypass_health_authority_verification bool DEFAULT false NOT NULL;
+
+END;
diff --git a/pkg/api/v1alpha1/verification_types.go b/pkg/api/v1alpha1/verification_types.go
index 24fa661..579baab 100644
--- a/pkg/api/v1alpha1/verification_types.go
+++ b/pkg/api/v1alpha1/verification_types.go
@@ -31,7 +31,7 @@ const (
 )
 
 // TransmissionRiskVector is an additional set of claims that can be
-// included in the verification certificatin for a diagnosis as recieved
+// included in the verification certificatin for a diagnosis as received
 // from a trusted public health authority.
 type TransmissionRiskVector []TransmissionRiskOverride
 
diff --git a/pkg/verification/utils.go b/pkg/verification/utils.go
index c18a474..79d7367 100644
--- a/pkg/verification/utils.go
+++ b/pkg/verification/utils.go
@@ -44,7 +44,9 @@ func CalculateExposureKeyHMAC(keys []model.ExposureKey, secret []byte) ([]byte,
 
 	cleartext := strings.Join(perKeyText, ",")
 	mac := hmac.New(sha256.New, secret)
-	mac.Write([]byte(cleartext))
+	if _, err := mac.Write([]byte(cleartext)); err != nil {
+		return nil, fmt.Errorf("failed to write hmac: %w", err)
+	}
 
 	return mac.Sum(nil), nil
 }
diff --git a/tools/admin-console/main.go b/tools/admin-console/main.go
index 34fbb72..39fbbdd 100644
--- a/tools/admin-console/main.go
+++ b/tools/admin-console/main.go
@@ -65,5 +65,7 @@ func main() {
 	router.POST("/siginfo/:id", saveSigInfoController.Execute)
 
 	log.Printf("listening on http://localhost:" + config.Port)
-	router.Run()
+	if err := router.Run(); err != nil {
+		log.Fatal(err)
+	}
 }
diff --git a/tools/export-generate/main.go b/tools/export-generate/main.go
index e3157f6..e10a6a3 100644
--- a/tools/export-generate/main.go
+++ b/tools/export-generate/main.go
@@ -61,11 +61,11 @@ func main() {
 		log.Fatal("--signing-key is required.")
 	}
 
-	// set endTime default to now, startTime default to (now - 24h)
+	// set endTime default to now, startTime default to (now - 24h).
 	endTime := time.Now()
 	startTime := endTime.Add(-time.Hour * 24)
 
-	// command-line flags can override startTime and endTime
+	// command-line flags can override startTime and endTime.
 	if *startTimestamp != "" {
 		var err error
 		startTime, err = time.Parse(time.RFC3339, *startTimestamp)
@@ -82,14 +82,14 @@ func main() {
 		}
 	}
 
-	// parse signing key
+	// parse signing key.
 	var privateKey *ecdsa.PrivateKey
 	privateKey, err := getSigningKey(*signingKey)
 	if err != nil {
 		log.Fatalf("unable to generate signing key: %v", err)
 	}
 
-	// generate fake keys
+	// generate fake keys.
 	var actualNumKeys int
 	var exposureKeys []publishmodel.Exposure
 	if *tekFile != "" {
@@ -99,7 +99,7 @@ func main() {
 			log.Fatalf("unable to read file: %v", err)
 		}
 		data := publishmodel.ExposureKeys{}
-		err = json.Unmarshal([]byte(file), &data)
+		err = json.Unmarshal(file, &data)
 		if err != nil {
 			log.Fatalf("unable to parse json: %v", err)
 		}
@@ -137,7 +137,7 @@ func main() {
 		}
 	}
 
-	// split up into batches
+	// split up into batches.
 	eb := &model.ExportBatch{
 		FilenameRoot:   *filenameRoot,
 		StartTimestamp: startTime,
@@ -167,11 +167,11 @@ func writeFile(eb *model.ExportBatch, currentBatch []*publishmodel.Exposure, b,
 		SigningKeyID:      *keyID,
 		SigningKeyVersion: *keyVersion,
 	}
-	signer := export.ExportSigners{
+	signer := &export.Signer{
 		SignatureInfo: signatureInfo,
 		Signer:        privateKey,
 	}
-	data, err := export.MarshalExportFile(eb, currentBatch, b, numBatches, []export.ExportSigners{signer})
+	data, err := export.MarshalExportFile(eb, currentBatch, b, numBatches, []*export.Signer{signer})
 	if err != nil {
 		log.Fatalf("error marshalling export file: %v", err)
 	}
@@ -188,19 +188,19 @@ func getSigningKey(fileName string) (*ecdsa.PrivateKey, error) {
 	return ParseECPrivateKeyFromPEM(keyBytes)
 }
 
-// Parse PEM encoded Elliptic Curve Private Key Structure
+// Parse PEM encoded Elliptic Curve Private Key Structure.
 func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
 	ErrNotECPrivateKey := errors.New("key is not a valid ECDSA private key")
 	ErrKeyMustBePEMEncoded := errors.New("invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
 	var err error
 
-	// Parse PEM block
+	// Parse PEM block.
 	var block *pem.Block
 	if block, _ = pem.Decode(key); block == nil {
 		return nil, ErrKeyMustBePEMEncoded
 	}
 
-	// Parse the key
+	// Parse the key.
 	var parsedKey interface{}
 	if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
 		return nil, err
diff --git a/tools/unwrap-signature/main.go b/tools/unwrap-signature/main.go
index 0d9f9c6..f0dea6b 100644
--- a/tools/unwrap-signature/main.go
+++ b/tools/unwrap-signature/main.go
@@ -46,7 +46,9 @@ func main() {
 	}
 
 	teksl := &export.TEKSignatureList{}
-	proto.Unmarshal(inData, teksl)
+	if err := proto.Unmarshal(inData, teksl); err != nil {
+		log.Fatalf("failed to unmarshal proto: %v", err)
+	}
 
 	log.Printf("Data: \n%v", teksl)
 	sig := teksl.Signatures[0].Signature


Test patch:
diff --git a/internal/android/safetynet_test.go b/internal/android/safetynet_test.go
index bc8e3ff..fdbccf2 100644
--- a/internal/android/safetynet_test.go
+++ b/internal/android/safetynet_test.go
@@ -202,8 +202,8 @@ func TestVerifyOptsFor(t *testing.T) {
 				AppPackageName:           "foo",
 				SafetyNetBasicIntegrity:  true,
 				SafetyNetCTSProfileMatch: true,
-				SafetyNetPastTime:        time.Duration(15 * time.Minute),
-				SafetyNetFutureTime:      time.Duration(1 * time.Second),
+				SafetyNetPastTime:        15 * time.Minute,
+				SafetyNetFutureTime:      1 * time.Second,
 			},
 			opts: &VerifyOpts{
 				AppPkgName:      "foo",
diff --git a/internal/authorizedapp/database/authorized_app_test.go b/internal/authorizedapp/database/authorized_app_test.go
index 6474c61..55918f6 100644
--- a/internal/authorizedapp/database/authorized_app_test.go
+++ b/internal/authorizedapp/database/authorized_app_test.go
@@ -168,27 +168,28 @@ func TestGetAuthorizedApp(t *testing.T) {
 			name: "safetynet_fileds",
 			sql: `
 				INSERT INTO AuthorizedApp (
-					app_package_name, platform, allowed_regions,
+					app_package_name, platform, allowed_regions, bypass_health_authority_verification,
 					safetynet_disabled, safetynet_apk_digest, safetynet_cts_profile_match, safetynet_basic_integrity
 				)
 				VALUES (
-					$1, $2, $3,
-					$4, $5, $6, $7
+					$1, $2, $3, $4,
+					$5, $6, $7, $8
 				)
 			`,
 			args: []interface{}{
-				"myapp", "android", []string{},
+				"myapp", "android", []string{}, true,
 				false, []string{"092fcfb", "252f10c"}, false, false,
 			},
 			exp: &model.AuthorizedApp{
-				AppPackageName:            "myapp",
-				Platform:                  "android",
-				AllowedRegions:            map[string]struct{}{},
-				AllowedHealthAuthorityIDs: map[int64]struct{}{},
-				SafetyNetDisabled:         false,
-				SafetyNetApkDigestSHA256:  []string{"092fcfb", "252f10c"},
-				SafetyNetBasicIntegrity:   false,
-				SafetyNetCTSProfileMatch:  false,
+				AppPackageName:                    "myapp",
+				Platform:                          "android",
+				AllowedRegions:                    map[string]struct{}{},
+				AllowedHealthAuthorityIDs:         map[int64]struct{}{},
+				BypassHealthAuthorityVerification: true,
+				SafetyNetDisabled:                 false,
+				SafetyNetApkDigestSHA256:          []string{"092fcfb", "252f10c"},
+				SafetyNetBasicIntegrity:           false,
+				SafetyNetCTSProfileMatch:          false,
 			},
 		},
 
diff --git a/internal/export/batcher_test.go b/internal/export/batcher_test.go
index e86288e..9375751 100644
--- a/internal/export/batcher_test.go
+++ b/internal/export/batcher_test.go
@@ -128,7 +128,6 @@ func TestMakeBatchRanges(t *testing.T) {
 					t.Errorf("unexpected range difference between start and end for index %d, got %v, want %v", i, got[i].end.Sub(got[i].start), tc.period)
 				}
 			}
-
 		})
 	}
 }
diff --git a/internal/export/database/export_test.go b/internal/export/database/export_test.go
index 3f13f15..383e90e 100644
--- a/internal/export/database/export_test.go
+++ b/internal/export/database/export_test.go
@@ -100,7 +100,9 @@ func TestLookupSignatureInfos(t *testing.T) {
 		},
 	}
 	for _, si := range want {
-		New(testDB).AddSignatureInfo(ctx, si)
+		if err := New(testDB).AddSignatureInfo(ctx, si); err != nil {
+			t.Fatalf("failed to add signature info %v: %v", si, err)
+		}
 	}
 
 	ids := []int64{want[0].ID, want[1].ID, want[2].ID}
diff --git a/internal/export/exportfile_test.go b/internal/export/exportfile_test.go
index 34fc761..0d8a7e1 100644
--- a/internal/export/exportfile_test.go
+++ b/internal/export/exportfile_test.go
@@ -75,9 +75,9 @@ func TestMarshalUnmarshalExportFile(t *testing.T) {
 
 	signer := &customTestSigner{}
 
-	blob, err := MarshalExportFile(batch, exposures, 1, 1, []ExportSigners{
-		{SignatureInfo: signatureInfo,
-			Signer: signer}})
+	blob, err := MarshalExportFile(batch, exposures, 1, 1, []*Signer{
+		{SignatureInfo: signatureInfo, Signer: signer},
+	})
 	if err != nil {
 		t.Fatalf("Can't marshal export file, %v", err)
 	}
diff --git a/internal/publish/database/exposure_test.go b/internal/publish/database/exposure_test.go
index b2f5335..36bb459 100644
--- a/internal/publish/database/exposure_test.go
+++ b/internal/publish/database/exposure_test.go
@@ -141,7 +141,6 @@ func TestExposures(t *testing.T) {
 	if diff := cmp.Diff(exposures[2:], got); diff != "" {
 		t.Errorf("DeleteExposures: mismatch (-want, +got):\n%s", diff)
 	}
-
 }
 
 func listExposures(ctx context.Context, db *PublishDB, c IterateExposuresCriteria) (_ []*model.Exposure, err error) {
diff --git a/internal/verification/database/health_authority_db_test.go b/internal/verification/database/health_authority_db_test.go
index fbc6504..66e82fe 100644
--- a/internal/verification/database/health_authority_db_test.go
+++ b/internal/verification/database/health_authority_db_test.go
@@ -77,7 +77,7 @@ func TestAddRetrieveHealthAuthorityKeys(t *testing.T) {
 	}
 
 	wantKeys := []*model.HealthAuthorityKey{
-		&model.HealthAuthorityKey{
+		{
 			Version:      "v1",
 			From:         time.Now().Truncate(time.Second),
 			PublicKeyPEM: validPEM,
diff --git a/internal/verification/model/health_authority_test.go b/internal/verification/model/health_authority_test.go
index 9eab202..7d4e647 100644
--- a/internal/verification/model/health_authority_test.go
+++ b/internal/verification/model/health_authority_test.go
@@ -117,8 +117,6 @@ NTG3+oqI0Q6a3kPOuAAAupr373j7O1YXrM2KAix966EPwTNlK7YCcJa0m6PKz9DT
 			} else if k == nil {
 				t.Errorf("ECDSA public key is unexpectedly nil")
 			}
-
 		})
 	}
-
 }
diff --git a/pkg/api/v1alpha1/verification_types_test.go b/pkg/api/v1alpha1/verification_types_test.go
index 97caf0e..0ef8a55 100644
--- a/pkg/api/v1alpha1/verification_types_test.go
+++ b/pkg/api/v1alpha1/verification_types_test.go
@@ -22,7 +22,6 @@ import (
 )
 
 func TestTransmissionRiskVectorSort(t *testing.T) {
-
 	got := TransmissionRiskVector{
 		{0, 0},
 		{3, 100},
diff --git a/testing/enclient/enclient.go b/testing/enclient/enclient.go
index 837a97b..b0ce497 100644
--- a/testing/enclient/enclient.go
+++ b/testing/enclient/enclient.go
@@ -22,7 +22,7 @@ type Interval int32
 // Posts requests to the specified url.
 // This methods attempts to serialize data argument as a json.
 func PostRequest(url string, data interface{}) (*http.Response, error) {
-	request := bytes.NewBuffer(JsonRequest(data))
+	request := bytes.NewBuffer(JSONRequest(data))
 	r, err := http.NewRequest("POST", url, request)
 	if err != nil {
 		return nil, err
@@ -48,7 +48,7 @@ func PostRequest(url string, data interface{}) (*http.Response, error) {
 }
 
 // Serializes the given argument to json.
-func JsonRequest(data interface{}) []byte {
+func JSONRequest(data interface{}) []byte {
 	jsonData, err := json.Marshal(data)
 	if err != nil {
 		log.Fatalf("unable to marshal json payload")