Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Issue582 (#597)
Browse files Browse the repository at this point in the history
* use enx redirect for enslinks

* add tf config

* review comments
  • Loading branch information
mikehelmick authored Sep 21, 2020
1 parent 201d171 commit 60b968c
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 29 deletions.
625 changes: 625 additions & 0 deletions cmd/server/assets/realm.html

Large diffs are not rendered by default.

31 changes: 23 additions & 8 deletions cmd/server/assets/realmadmin/_form_codes.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,27 +180,39 @@
{{if $realm.EnableENExpress}}
Your SMS template <em>MUST</em> contain <code>[enslink]</code>.
<ul>
<li><code>[enslink]</code> Inserts the required EN Express link of: <code>ens://v?r=[region]&c=[longcode]</code></li>
{{if eq "" .enxRedirectDomain}}
<li><code>[enslink]</code> Inserts the required EN Express link of: <code>ens://v?r=[region]&c=[longcode]</code></li> <li><code>[enslink]</code> Inserts the required EN Express link of: <code>ens://v?r=[region]&c=[longcode]</code></li>
{{else}}
<li><code>[enslink]</code> Inserts the EN Express link of: <code>https://{{toLower $realm.RegionCode}}.{{.enxRedirectDomain}}/v?c=[longcode]</code>
<ul>
<li>This domain should be registered as a universal link for both your Android and iOS applications.</li>
<li>Contact your server operator to verify the the verification EN Express redirect service is running and configurd correctly.</li>
</ul>
</li>
{{end}}
<li><code>[longexpires]</code>The number of hours until the long code expires (just the number, no units).</li>
</ul>

Here is an example SMS template using EN Express.

<ul>
<li>
<p>Custom greeting before the EN Express link and showing expiration.
This is <code>145</code> characters when expanded.</p>
<p>Custom greeting before the EN Express link and showing expiration.</p>
<p>
<samp class="text-dark">
State of Wonder Dept. of Health. Click to share anonymous data for exposure notifications [enslink] Expires in [longexpires] hours
State of Wonder DOH. Click to share anonymous data for exposure notifications [enslink] (mobile only) Expires in [longexpires] hours
</samp>
</p>
</li>
<li>
<p>This results in a SMS message that looks like:</p>
<p>
<samp class="text-dark">
State of Wonder Dept. of Health. Click to share anonymous data for exposure notifications ens://v?r=US-XX&c=12345678abcd1234 Expires in 24 hours
{{if eq "" .enxRedirectDomain}}
State of Wonder DOH. Click to share anonymous data for exposure notifications ens://v?r={{$realm.RegionCode}}&c=[longcode] (mobile only) Expires in 24 hours
{{else}}
State of Wonder DOH. Click to share anonymous data for exposure notifications https://{{toLower $realm.RegionCode}}.{{.enxRedirectDomain}}/v?c=[longcode] (mobile only) Expires in 24 hours
{{end}}
</samp>
</p>
</li>
Expand Down Expand Up @@ -231,12 +243,15 @@
</p>
</li>
<li>
<p>Send long code with custom URI (<code>152</code> characters with 16 digit codes and 24 hour expiration):</p>
<p>Send long code with custom URI (<code>152</code> characters with 16 digit codes and 24 hour expiration).
Here we assume that <code>verify.mypha.gov</code> is registred as a universal link for both your iOS
and Android applications.
</p>
<p>
<samp class="text-dark">
You have tested positive for Covid-19. Click here to
You have tested positive for COVID-19. Click here to
share anonymous data for exposure notifications
dohen://v?c=[longcode] (Expires in [longexpires] hours)
https://verify.mypha.gov/v?c=[longcode] (Expires in [longexpires] hours)
</samp>
</p>
</li>
Expand Down
12 changes: 12 additions & 0 deletions pkg/config/admin_server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package config

import (
"context"
"strings"
"time"

"github.com/google/exposure-notifications-verification-server/pkg/cache"
Expand Down Expand Up @@ -48,6 +49,11 @@ type AdminAPIServerConfig struct {
CollisionRetryCount uint `env:"COLLISION_RETRY_COUNT,default=6"`
AllowedSymptomAge time.Duration `env:"ALLOWED_PAST_SYMPTOM_DAYS,default=336h"` // 336h is 14 days.
EnforceRealmQuotas bool `env:"ENFORCE_REALM_QUOTAS, default=false"`

// For EN Express, the link will be
// https://[realm-region].[ENX_REDIRECT_DOMAIN]/v?c=[longcode]
// This repository contains a redirect service that can be used for this purpose.
ENExpressRedirectDomain string `env:"ENX_REDIRECT_DOMAIN"`
}

// NewAdminAPIServerConfig returns the environment config for the Admin API server.
Expand Down Expand Up @@ -75,9 +81,15 @@ func (c *AdminAPIServerConfig) Validate() error {
}
}

c.ENExpressRedirectDomain = strings.ToLower(c.ENExpressRedirectDomain)

return nil
}

func (c *AdminAPIServerConfig) GetENXRedirectDomain() string {
return c.ENExpressRedirectDomain
}

func (c *AdminAPIServerConfig) GetCollisionRetryCount() uint {
return c.CollisionRetryCount
}
Expand Down
1 change: 1 addition & 0 deletions pkg/config/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ type IssueAPIConfig interface {
GetAllowedSymptomAge() time.Duration
GetEnforceRealmQuotas() bool
GetRateLimitConfig() *ratelimit.Config
GetENXRedirectDomain() string
}
11 changes: 11 additions & 0 deletions pkg/config/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ type ServerConfig struct {

AssetsPath string `env:"ASSETS_PATH,default=./cmd/server/assets"`

// For EN Express, the link will be
// https://[realm-region].[ENX_REDIRECT_DOMAIN]/v?c=[longcode]
// This repository contains a redirect service that can be used for this purpose.
ENExpressRedirectDomain string `env:"ENX_REDIRECT_DOMAIN"`

// Certificate signing key settings, needed for public key / settings display.
CertificateSigning CertificateSigningConfig

Expand Down Expand Up @@ -119,9 +124,15 @@ func (c *ServerConfig) Validate() error {
}
}

c.ENExpressRedirectDomain = strings.ToLower(c.ENExpressRedirectDomain)

return nil
}

func (c *ServerConfig) GetENXRedirectDomain() string {
return c.ENExpressRedirectDomain
}

func (c *ServerConfig) GetCollisionRetryCount() uint {
return c.CollisionRetryCount
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/issueapi/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func (c *Controller) HandleIssue() http.Handler {
}

if request.Phone != "" && smsProvider != nil {
message := realm.BuildSMSText(code, longCode)
message := realm.BuildSMSText(code, longCode, c.config.GetENXRedirectDomain())
if err := smsProvider.SendSMS(ctx, request.Phone, message); err != nil {
// Delete the token
if err := c.db.DeleteVerificationCode(code); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/realmadmin/express.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (c *Controller) HandleEnableExpress() http.Handler {
realm.CodeDuration = enxSettings.CodeDuration
realm.LongCodeLength = enxSettings.LongCodeLength
realm.LongCodeDuration = enxSettings.LongCodeDuration
realm.SMSTextTemplate = "This is your Exposure Notifications Verification code: [enslink] Expires in [longexpires] hours"
realm.SMSTextTemplate = "Your Exposure Notifications verification link: [enslink]. Expires in [longexpires] hours (click for mobile device only)"
// Confirmed is the only allowed test type for EN Express.
realm.AllowedTestTypes = database.TestTypeConfirmed

Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/realmadmin/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,11 @@ func (c *Controller) renderSettings(ctx context.Context, w http.ResponseWriter,
// Valid settings for pwd rotation.
m["passwordRotateDays"] = passwordRotationPeriodDays
m["passwordWarnDays"] = passwordRotationWarningDays

// Valid settings for code parameters.
m["shortCodeLengths"] = shortCodeLengths
m["shortCodeMinutes"] = shortCodeMinutes
m["longCodeLengths"] = longCodeLengths
m["longCodeHours"] = longCodeHours
m["enxRedirectDomain"] = c.config.GetENXRedirectDomain()
c.h.RenderHTML(w, "realmadmin/edit", m)
}
17 changes: 13 additions & 4 deletions pkg/database/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type Realm struct {
LongCodeLength uint `gorm:"type:smallint; not null; default: 16"`
LongCodeDuration DurationSeconds `gorm:"type:bigint; not null; default: 86400"` // default 24h
// SMS Content
SMSTextTemplate string `gorm:"type:varchar(400); not null; default: 'This is your Exposure Notifications Verification code: ens://v?r=[region]&c=[longcode] Expires in [longexpires] hours'"`
SMSTextTemplate string `gorm:"type:varchar(400); not null; default: 'This is your Exposure Notifications Verification code: [longcode] Expires in [longexpires] hours'"`

// WelcomeMessage is arbitrary realm-defined data to display to users after
// selecting this realm. If empty, nothing is displayed. The format is
Expand Down Expand Up @@ -173,7 +173,7 @@ func NewRealmWithDefaults(name string) *Realm {
CodeDuration: FromDuration(15 * time.Minute),
LongCodeLength: 16,
LongCodeDuration: FromDuration(24 * time.Hour),
SMSTextTemplate: "This is your Exposure Notifications Verification code: ens://v?r=[region]&c=[longcode] Expires in [longexpires] hours",
SMSTextTemplate: "This is your Exposure Notifications Verification code: [longcode] Expires in [longexpires] hours",
AllowedTestTypes: 14,
CertificateDuration: FromDuration(15 * time.Minute),
}
Expand Down Expand Up @@ -294,10 +294,19 @@ func (r *Realm) GetLongCodeDurationHours() int {
}

// BuildSMSText replaces certain strings with the right values.
func (r *Realm) BuildSMSText(code, longCode string) string {
func (r *Realm) BuildSMSText(code, longCode string, enxDomain string) string {
text := r.SMSTextTemplate

text = strings.ReplaceAll(text, SMSENExpressLink, fmt.Sprintf("ens://v?r=%s&c=%s", SMSRegion, SMSLongCode))
if enxDomain == "" {
// preserves legacy behavior.
text = strings.ReplaceAll(text, SMSENExpressLink, fmt.Sprintf("ens://v?r=%s&c=%s", SMSRegion, SMSLongCode))
} else {
text = strings.ReplaceAll(text, SMSENExpressLink,
fmt.Sprintf("https://%s.%s/v?c=%s",
strings.ToLower(r.RegionCode),
enxDomain,
SMSLongCode))
}
text = strings.ReplaceAll(text, SMSRegion, r.RegionCode)
text = strings.ReplaceAll(text, SMSCode, code)
text = strings.ReplaceAll(text, SMSExpires, fmt.Sprintf("%d", r.GetCodeDurationMinutes()))
Expand Down
27 changes: 13 additions & 14 deletions pkg/database/realm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,25 @@

package database

import "testing"
import (
"testing"
)

func TestSMS(t *testing.T) {
realm := NewRealmWithDefaults("test")
realm.SMSTextTemplate = "This is your Exposure Notifications Verification code: [enslink] Expires in [longexpires] hours"
realm.RegionCode = "US-WA"

{
got := realm.BuildSMSText("12345678", "abcdefgh12345678")
want := "This is your Exposure Notifications Verification code: ens://v?r=US-WA&c=abcdefgh12345678 Expires in 24 hours"
if got != want {
t.Errorf("SMS text wrong, want: %q got %q", want, got)
}
got := realm.BuildSMSText("12345678", "abcdefgh12345678", "en.express")
want := "This is your Exposure Notifications Verification code: https://us-wa.en.express/v?c=abcdefgh12345678 Expires in 24 hours"
if got != want {
t.Errorf("SMS text wrong, want: %q got %q", want, got)
}

{
realm.SMSTextTemplate = "State of Wonder, Covid-19 Exposure Verification code [code]. Expires in [expires] minutes. Act now!"
got := realm.BuildSMSText("654321", "asdflkjasdlkfjl")
want := "State of Wonder, Covid-19 Exposure Verification code 654321. Expires in 15 minutes. Act now!"
if got != want {
t.Errorf("SMS text wrong, want: %q got %q", want, got)
}
realm.SMSTextTemplate = "State of Wonder, COVID-19 Exposure Verification code [code]. Expires in [expires] minutes. Act now!"
got = realm.BuildSMSText("654321", "asdflkjasdlkfjl", "")
want = "State of Wonder, COVID-19 Exposure Verification code 654321. Expires in 15 minutes. Act now!"
if got != want {
t.Errorf("SMS text wrong, want: %q got %q", want, got)
}
}
2 changes: 2 additions & 0 deletions pkg/render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func templateFuncs() template.FuncMap {
"joinStrings": strings.Join,
"trimSpace": strings.TrimSpace,
"stringContains": strings.Contains,
"toLower": strings.ToLower,
"toUpper": strings.ToUpper,
}
}

Expand Down
1 change: 1 addition & 0 deletions terraform/service_admin_apiserver.tf
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ resource "google_cloud_run_service" "adminapi" {
local.database_config,
local.gcp_config,
local.rate_limit_config,
local.issue_config,

// This MUST come last to allow overrides!
lookup(var.service_environment, "adminapi", {}),
Expand Down
1 change: 1 addition & 0 deletions terraform/service_server.tf
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ resource "google_cloud_run_service" "server" {
local.rate_limit_config,
local.session_config,
local.signing_config,
local.issue_config,

// This MUST come last to allow overrides!
lookup(var.service_environment, "server", {}),
Expand Down
4 changes: 4 additions & 0 deletions terraform/services.tf
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ locals {
VERIFICATION_SERVER_API = google_cloud_run_service.apiserver.status.0.url
}

issue_config = {
ENX_REDIRECT_DOMAIN = var.enx_redirect_domain
}

enx_redirect_config = {
ASSETS_PATH = "/assets"
HOSTNAME_TO_REGION = join(",", [for o in var.enx_redirect_domain_map : format("%s=%s", o.host, o.region)])
Expand Down
6 changes: 6 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ variable "adminapi-host" {
description = "Domain adminapi is hosted on."
}

variable "enx_redirect_domain" {
type = string
default = ""
description = "TLD for enx-redirect service links."
}

variable "enx_redirect_domain_map" {
type = list(object({
region = string
Expand Down

0 comments on commit 60b968c

Please sign in to comment.