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

Issue582 #597

Merged
merged 3 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just do {{if .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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like this line is twice (see column like 120)

{{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),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we currently enforce uniqueness on region codes right now, just realm names. Should we add a database constraint for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can file an issue for that. I'm not confident it would help anything since it's only specific to one sever

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