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

Commit

Permalink
Allow the issuer to select an SMS template (#1352)
Browse files Browse the repository at this point in the history
* Show templates on code issue

* Use it

* check expansion

* expansion error

* warn

* review things

* check test err

* Google translate

* id
  • Loading branch information
whaught authored Dec 15, 2020
1 parent c8b38a1 commit 798f9ff
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 18 deletions.
23 changes: 22 additions & 1 deletion cmd/server/assets/codes/issue.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,26 @@ <h1>{{t $.locale "codes.issue.header"}}</h1>
<div class="card mb-3 shadow-sm">
<div class="card-header">{{t $.locale "codes.issue.sms-text-message-header"}}</div>
<div class="card-body">
{{if $currentRealm.SMSTextAlternateTemplates}}
<div class="row form-group">
<label for="symptomDate" class="col-sm-6 col-md-4 col-lg-3">{{t $.locale "codes.issue.sms-text-message-label"}}</label>
<label for="sms-template" class="col-sm-6 col-md-4 col-lg-3">{{t $.locale "codes.issue.sms-template-label"}}</label>
<div class="col-sm-6 col-md-8 col-lg-9">
<div class="input-group">
<select class="form-control" id="sms-template">
<option value="Default SMS template">Default SMS template</option>
{{range $k, $v := $currentRealm.SMSTextAlternateTemplates}}
<option value="{{$k}}">{{$k}}</option>
{{end}}
</select>
</div>
<small class="form-text text-muted">
{{t $.locale "codes.issue.sms-template-detail"}}
</small>
</div>
</div>
{{end}}
<div class="row form-group">
<label for="phone" class="col-sm-6 col-md-4 col-lg-3">{{t $.locale "codes.issue.sms-text-message-label"}}</label>
<div class="col-sm-6 col-md-8 col-lg-9">
<div class="input-group">
<input type="tel" id="phone" name="phone" class="form-control" autocomplete="off" class="w-100" />
Expand Down Expand Up @@ -231,6 +249,7 @@ <h1>{{t $.locale "codes.issue.header"}}</h1>
let $form;
let $inputTestDate;
let $inputSymptomDate;
let $inputSMSTemplate;
let $inputPhone;
let $buttonSubmit;
let $buttonReset;
Expand All @@ -253,6 +272,7 @@ <h1>{{t $.locale "codes.issue.header"}}</h1>
$form = $('form#issue');
$inputTestDate = $('input#test-date');
$inputSymptomDate = $('input#symptom-date');
$inputSMSTemplate = $('select#sms-template');
$inputPhone = $('input#phone');
$buttonSubmit = $('button#submit');
$buttonReset = $('button#reset');
Expand Down Expand Up @@ -304,6 +324,7 @@ <h1>{{t $.locale "codes.issue.header"}}</h1>
data.tzOffset = new Date().getTimezoneOffset();

{{if $hasSMSConfig}}
data['smsTemplateLabel'] = $inputSMSTemplate.val();
data['phone'] = iti.getNumber();
{{end}}

Expand Down
5 changes: 5 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ Request a verification code to be issued. Accepts [optional] symptom date and te
"testType": "<valid test type>",
"tzOffset": 0,
"phone": "+CC Phone number",
"smsTemplateLabel": "my sms template",
"padding": "<bytes>",
"uuid": "string UUID",
"externalIssuerID": "external-ID",
Expand All @@ -207,6 +208,10 @@ Request a verification code to be issued. Accepts [optional] symptom date and te
* `phone`
* Phone number to send the SMS to. If a phone number is provided, but the SMS text
message fails to send, the API will return a 4xx client error.
* `smsTemplateLabel`
* If the realm has more than one SMS template defined, this may be optionally specify
the label of the message template which the server should compose. If omitted, the
default template will be used.
* `padding` is a _recommended_ field that obfuscates the size of the request
body to a network observer. The client should generate and insert a random
number of base64-encoded bytes into this field. The server does not process
Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/de/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "SMS Textnachricht (empfohlen)"
msgid "codes.issue.sms-text-message-label"
msgstr "Patienten Telefonnummer"

msgid "codes.issue.sms-template-label"
msgstr "SMS Vorlage"

msgid "codes.issue.sms-template-detail"
msgstr "Der Patient erhält eine SMS mit der ausgewählten Nachrichtenvorlage."

msgid "codes.issue.sms-text-message-detail"
msgstr "Das System sendet dem Patienten eine Textnachricht mit dem Verifizierungscode. Die eingetragene Telefonnummer muss Textnachrichten erhalten können."

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/en/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "SMS text message (recommended)"
msgid "codes.issue.sms-text-message-label"
msgstr "Patient phone number"

msgid "codes.issue.sms-template-label"
msgstr "SMS template"

msgid "codes.issue.sms-template-detail"
msgstr "The patient will receive an SMS with the selected message template."

msgid "codes.issue.sms-text-message-detail"
msgstr "If provided, the system will send a text message containing the code to the patient. This must be a phone number capable of receiving SMS text messages."

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/es/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "Mensaje de texto SMS (recomendado)"
msgid "codes.issue.sms-text-message-label"
msgstr "Número telefónico del paciente"

msgid "codes.issue.sms-template-label"
msgstr "Plantilla de SMS"

msgid "codes.issue.sms-template-detail"
msgstr "El paciente recibirá un SMS con la plantilla de mensaje seleccionada."

msgid "codes.issue.sms-text-message-detail"
msgstr "El sistema enviará un mensaje de texto conteniendo el código al paciente a este número, si es provisto. El telefóno deberá ser capaz de recibir mensajes de texto SMS."

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/fr/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "Message SMS (recommandé)"
msgid "codes.issue.sms-text-message-label"
msgstr "Numéro de téléphone du patient"

msgid "codes.issue.sms-template-label"
msgstr "Modèle de SMS"

msgid "codes.issue.sms-template-detail"
msgstr "Le patient recevra un SMS avec le modèle de message sélectionné."

msgid "codes.issue.sms-text-message-detail"
msgstr "S'il est fourni, le système enverra au patient par SMS un message textuel contenant le code. Ce numéro doit être capabe de recevoir des messages SMS."

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/it/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "Messaggio di testo SMS (consigliato)"
msgid "codes.issue.sms-text-message-label"
msgstr "Numero di telefono del paziente"

msgid "codes.issue.sms-template-label"
msgstr "Modello SMS"

msgid "codes.issue.sms-template-detail"
msgstr "Il paziente riceverà un SMS con il modello di messaggio selezionato."

msgid "codes.issue.sms-text-message-detail"
msgstr "Se fornito, il sistema inviera' un messaggio di testo con il codice al paziente. Il telefono deve essere in grado di ricevere messaggi di testo SMS."

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/ja/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "SMSテキストメッセージ(推奨)"
msgid "codes.issue.sms-text-message-label"
msgstr "患者の電話番号"

msgid "codes.issue.sms-template-label"
msgstr "SMSテンプレート"

msgid "codes.issue.sms-template-detail"
msgstr "患者は、選択したメッセージテンプレートを含むSMSを受信します"

msgid "codes.issue.sms-text-message-detail"
msgstr "電話番号が提供されれば、システムは患者にコードを記述したテキストメッセージを送信します。SMSテキストメッセージを受信できる電話番号が必要です。"

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/pt/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "Mensagem de texto SMS (recomendado)"
msgid "codes.issue.sms-text-message-label"
msgstr "Número de telefone do paciente"

msgid "codes.issue.sms-template-label"
msgstr "Modelo de SMS"

msgid "codes.issue.sms-template-detail"
msgstr "O paciente receberá um SMS com o modelo de mensagem selecionado."

msgid "codes.issue.sms-text-message-detail"
msgstr "Se for providenciado, o sistema enviará uma mensagem de texto contendo o código ao paciente. O número precisa estar habilitado para receber mensagens de texto SMS."

Expand Down
6 changes: 6 additions & 0 deletions internal/i18n/locales/tr/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ msgstr "SMS kısa mesaj (önerilen)"
msgid "codes.issue.sms-text-message-label"
msgstr "Hastanın telefon numarası"

msgid "codes.issue.sms-template-label"
msgstr "SMS şablonu"

msgid "codes.issue.sms-template-detail"
msgstr "Hasta, seçilen mesaj şablonunu içeren bir SMS alacaktır."

msgid "codes.issue.sms-text-message-detail"
msgstr "Bu alan doldurulursa, sistem üretilen kodu hastaya kısa mesaj (SMS) olarak atacaktır. O yüzden bu numara SMS alabilen bir numara olmalıdır."

Expand Down
5 changes: 3 additions & 2 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ type IssueCodeRequest struct {
TestType string `json:"testType"`
// Offset in minutes of the user's timezone. Positive, negative, 0, or omitted
// (using the default of 0) are all valid. 0 is considered to be UTC.
TZOffset float32 `json:"tzOffset"`
Phone string `json:"phone"`
TZOffset float32 `json:"tzOffset"`
Phone string `json:"phone"`
SMSTemplateLabel string `json:"smsTemplateLabel"`

// Optional: UUID is a handle which allows the issuer to track status
// of the issued verification code. If omitted the server will generate the UUID.
Expand Down
5 changes: 4 additions & 1 deletion pkg/controller/issueapi/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,10 @@ func (c *Controller) issue(ctx context.Context, authApp *database.AuthorizedApp,
if err := func() error {
defer observability.RecordLatency(ctx, time.Now(), mSMSLatencyMs, &result.obsBlame, &result.obsResult)

message := realm.BuildSMSText(code, longCode, c.config.GetENXRedirectDomain())
message, err := realm.BuildSMSText(code, longCode, c.config.GetENXRedirectDomain(), request.SMSTemplateLabel)
if err != nil {
return err
}

if err := smsProvider.SendSMS(ctx, request.Phone, message); err != nil {
// Delete the token
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/middleware/csrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func ConfigureCSRF(ctx context.Context, config *config.ServerConfig, h render.Re
}
}

// handleCSRFError is an http.HandlerFunc that can be installed inthe gorilla csrf
// handleCSRFError is an http.HandlerFunc that can be installed in the gorilla csrf
// protect middleware. It will respond w/ a JSON object containing error: on API
// requests and a signout redirect to other requests.
func handleCSRFError(ctx context.Context, h render.Renderer) http.Handler {
Expand Down
35 changes: 24 additions & 11 deletions pkg/database/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,15 +377,6 @@ func (r *Realm) BeforeSave(tx *gorm.DB) error {
}
}

// Check expansion length based on settings.
fakeCode := fmt.Sprintf(fmt.Sprintf("\\%0%d\\%d", r.CodeLength), 0)
fakeLongCode := fmt.Sprintf(fmt.Sprintf("\\%0%d\\%d", r.LongCodeLength), 0)
enxDomain := os.Getenv("ENX_REDIRECT_DOMAIN")
expandedSMSText := r.BuildSMSText(fakeCode, fakeLongCode, enxDomain)
if l := len(expandedSMSText); l > SMSTemplateExpansionMax {
r.AddError("SMSTextTemplate", fmt.Sprintf("when expanded, the result message is too long (%v characters). The max expanded message is %v characters", l, SMSTemplateExpansionMax))
}

if r.UseSystemEmailConfig && !r.CanUseSystemEmailConfig {
r.AddError("useSystemEmailConfig", "is not allowed on this realm")
}
Expand Down Expand Up @@ -461,6 +452,21 @@ func (r *Realm) validateSMSTemplate(label, t string) {
r.AddError("SMSTextTemplate", fmt.Sprintf("must be %d characters or less, current message is %v characters long", SMSTemplateMaxLength, l))
r.AddError(label, fmt.Sprintf("must contain %q", SMSENExpressLink))
}

// Check expansion length based on settings.
fakeCode := fmt.Sprintf(fmt.Sprintf("\\%0%d\\%d", r.CodeLength), 0)
fakeLongCode := fmt.Sprintf(fmt.Sprintf("\\%0%d\\%d", r.LongCodeLength), 0)
enxDomain := os.Getenv("ENX_REDIRECT_DOMAIN")
expandedSMSText, err := r.BuildSMSText(fakeCode, fakeLongCode, enxDomain, label)
if err != nil {
r.AddError("SMSTextTemplate", fmt.Sprintf("SMS template expansion failed: %s", err))
r.AddError(label, fmt.Sprintf("SMS template expansion failed: %s", err))
}
if l := len(expandedSMSText); l > SMSTemplateExpansionMax {
r.AddError("SMSTextTemplate", fmt.Sprintf("when expanded, the result message is too long (%v characters). The max expanded message is %v characters", l, SMSTemplateExpansionMax))
r.AddError(label, fmt.Sprintf("when expanded, the result message is too long (%v characters). The max expanded message is %v characters", l, SMSTemplateExpansionMax))
}

}

// GetCodeDurationMinutes is a helper for the HTML rendering to get a round
Expand All @@ -487,8 +493,15 @@ func (r *Realm) FindVerificationCodeByUUID(db *Database, uuid string) (*Verifica
}

// BuildSMSText replaces certain strings with the right values.
func (r *Realm) BuildSMSText(code, longCode string, enxDomain string) string {
func (r *Realm) BuildSMSText(code, longCode string, enxDomain, templateLabel string) (string, error) {
text := r.SMSTextTemplate
if templateLabel != "" && templateLabel != DefaultTemplateLabel && r.SMSTextAlternateTemplates != nil {
if t, has := r.SMSTextAlternateTemplates[templateLabel]; has && t != nil && *t != "" {
text = *t
} else {
return "", fmt.Errorf("no template found for label %s", templateLabel)
}
}

if enxDomain == "" {
// preserves legacy behavior.
Expand All @@ -506,7 +519,7 @@ func (r *Realm) BuildSMSText(code, longCode string, enxDomain string) string {
text = strings.ReplaceAll(text, SMSLongCode, longCode)
text = strings.ReplaceAll(text, SMSLongExpires, fmt.Sprintf("%d", r.GetLongCodeDurationHours()))

return text
return text, nil
}

// BuildInviteEmail replaces certain strings with the right values for invitations.
Expand Down
10 changes: 8 additions & 2 deletions pkg/database/realm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ func TestSMS(t *testing.T) {
realm.SMSTextTemplate = "This is your Exposure Notifications Verification code: [enslink] Expires in [longexpires] hours"
realm.RegionCode = "US-WA"

got := realm.BuildSMSText("12345678", "abcdefgh12345678", "en.express")
got, err := realm.BuildSMSText("12345678", "abcdefgh12345678", "en.express", "")
if err != nil {
t.Fatalf("failed to buildSMS, %v", err)
}
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", "")
got, err = realm.BuildSMSText("654321", "asdflkjasdlkfjl", "", "")
if err != nil {
t.Fatalf("failed to buildSMS, %v", err)
}
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)
Expand Down

0 comments on commit 798f9ff

Please sign in to comment.