Documentation
¶
Overview ¶
Package email holds what backd knows about the emails it asks a realm's delivery function to send: the kinds, the templates (loaded from <realm>/email/ into memory at startup and rendered per message), and the shape of the message handed to the function. backd never sends mail itself.
Index ¶
- Constants
- Variables
- func CompileOverride(kind string, o Override) (*CompiledOverride, []OverrideProblem)
- func DefaultFiles(kind string) (map[string][]byte, error)
- func DefaultPage(kind string) ([]byte, error)
- func ParseMustache(src string) (*Mustache, *SyntaxError)
- func RenderBuiltin(kind string, d PageData) (string, error)
- func RequiredPlaceholders(kind string) []string
- func SanitizeHTML(in string) (out string, removed []string)
- func ValidKind(v string) bool
- func ValidLocale(v string) bool
- type CompiledOverride
- type Data
- type Message
- type Mustache
- type Override
- type OverrideProblem
- type PageData
- type Pages
- type Purpose
- type SyntaxError
- type Templates
- type User
Constants ¶
const ( VerifyEmail = "verify-email" ResetPassword = "reset-password" AccountExists = "account-exists" PasswordChanged = "password-changed" Welcome = "welcome" ChangeEmail = "change-email" EmailChanged = "email-changed" Invitation = "invitation" // OrgInvitation invites to an organization ({{.Link}}, {{.Data.org_name}}, {{.Data.roles}}, // {{.ExpiresAt}}). OrgInvitation = "org-invitation" // SecurityNotice tells the user a change to their sign-in security (a // second factor on or off, a password set or removed, a key created); the // template reads {{.Data.event}}. MFAFailed warns of wrong second-factor codes // after a right password. SecurityNotice = "security-notice" MFAFailed = "mfa-failed" // ReauthCode carries the 6-digit code that proves a user with no password and no // second factor is there before a sensitive action ({{.Data.code}}, {{.ExpiresAt}}). ReauthCode = "reauth-code" // MFAReset tells a user that an administrator removed their second factor. MFAReset = "mfa-reset" // MagicLink carries the link and the 6-digit code that sign a user in // ({{.Link}}, {{.Data.code}}, {{.ExpiresAt}}); NoAccount tells the owner of an address with // no account that someone asked to sign in with it. MagicLink = "magic-link" NoAccount = "no-account" // TemplateFailed tells an organization's managers that a template they wrote couldn't be // used at send time ({{.Data.event}} is the kind, {{.Data.org_name}} the organization). TemplateFailed = "email-template-failed" // ConsentUpdated asks people to review the new version of a consent // ({{.Data.event}} is the consent's name); it is only sent by `backd consents notify`. ConsentUpdated = "consent-updated" )
The kinds of email backd sends on its own.
const ( PageVerifyEmail = "verify-email" PageResetPassword = "reset-password" PageConfirmEmailChange = "confirm-email-change" PageRevertEmailChange = "revert-email-change" PageAcceptInvitation = "accept-invitation" PageResult = "result" PageInvalidToken = "invalid-token" // PageOAuthError is shown when a provider sends the user back with a sign-in // attempt backd can't trace to an app. A realm may write its own // (<realm>/pages/oauth-error/<locale>.html); without one the built-in English // page is shown. It is not among PageKinds: a realm that only sends email // doesn't need it. PageOAuthError = "oauth-error" // PageMagicLink is what a magic link opens: a button that signs in (Kind "confirm"), or, // when the request has nowhere to send the browser, the instruction to type the code in the // app (Kind "type-code"). Optional like PageOAuthError: the built-in English page is // used when a realm has none. PageMagicLink = "magic-link" )
The hosted pages. The first five are the forms a link opens; Result is shown after an action succeeds, InvalidToken for any link that doesn't work.
const DirName = "email"
DirName is the folder of a realm that holds its email templates.
const (
// MaxTemplateBytes is the size of each of subject, text and HTML of an override.
MaxTemplateBytes = 64 << 10
)
Limits of an owner template, so one can't be made to cost much.
const PagesDirName = "pages"
PagesDirName is the folder of a realm that holds its hosted pages: the pages the links in emails open (<realm>/pages/<kind>/<locale>.html).
Variables ¶
var ( // EscapeHTML escapes a value for the HTML part. EscapeHTML = html.EscapeString // EscapeText makes a value safe for the text part and the subject: no control characters // except a newline and a tab. EscapeText = func(s string) string { return strings.Map(func(r rune) rune { if r < ' ' && r != '\n' && r != '\t' || r == 0x7f { return -1 } return r }, s) } )
Escapers: HTML for the HTML part, and for text and subject a plain one that only drops control characters (text isn't HTML, and the subject is one line).
var BuiltInKinds = []string{OrgInvitation, SecurityNotice, MFAFailed, ReauthCode, MFAReset, MagicLink, NoAccount, TemplateFailed, ConsentUpdated}
BuiltInKinds are system kinds a realm does not have to ship: when its email folder has none, the built-in English text is used for every listed locale, so a realm that existed before them keeps working. A folder of the kind replaces the built-in text.
var OptionalPageKinds = []string{PageOAuthError, PageMagicLink}
OptionalPageKinds are pages a realm may have in addition to PageKinds.
var PageKinds = []string{PageVerifyEmail, PageResetPassword, PageConfirmEmailChange, PageRevertEmailChange, PageAcceptInvitation, PageResult, PageInvalidToken}
PageKinds are the pages every realm that sends email has, in every language it lists.
var RequiredKinds = []string{VerifyEmail, ResetPassword, AccountExists, PasswordChanged}
RequiredKinds must have a template in every realm that configures email.
var SystemKinds = []string{VerifyEmail, ResetPassword, AccountExists, PasswordChanged, Welcome, ChangeEmail, EmailChanged, Invitation, OrgInvitation, SecurityNotice, MFAFailed, ReauthCode, MFAReset, MagicLink, NoAccount, TemplateFailed, ConsentUpdated}
SystemKinds are the kinds backd knows; `backd template realm` writes an English template for each.
Functions ¶
func CompileOverride ¶ added in v0.8.3
func CompileOverride(kind string, o Override) (*CompiledOverride, []OverrideProblem)
CompileOverride parses an override and checks it: sizes, syntax (with where), and the placeholders its kind needs in the text and the HTML.
func DefaultFiles ¶
DefaultFiles returns the English template files of a system kind, by file name (en.subject.txt, en.txt, en.html).
func DefaultPage ¶
DefaultPage returns the English template of a hosted page.
func ParseMustache ¶ added in v0.8.3
func ParseMustache(src string) (*Mustache, *SyntaxError)
ParseMustache parses src, or says what is wrong and where.
func RenderBuiltin ¶ added in v0.8.2
RenderBuiltin renders the built-in English page of a kind.
func RequiredPlaceholders ¶ added in v0.8.3
RequiredPlaceholders are the values a template of kind must print, so the message still does its job: the link of a token kind, and for an invitation the organization it is to.
func SanitizeHTML ¶ added in v0.8.3
SanitizeHTML makes the HTML of an owner template safe to put in an email (design/team-access.md §11): it keeps formatting tags, tables, links (https and mailto), images (https) and a short list of inline styles, and drops everything else: scripts, styles, forms, frames, event handlers, unknown tags (their text stays) and comments. The result is built from what was understood, not the input's own bytes, so what the parser misreads can't come through. removed lists, without repeats, what was dropped, for the person who wrote the template.
func ValidLocale ¶
ValidLocale reports whether v looks like a BCP 47 language tag (es, es-MX).
Types ¶
type CompiledOverride ¶ added in v0.8.3
type CompiledOverride struct {
// contains filtered or unexported fields
}
CompiledOverride is a checked override, ready to render.
func (*CompiledOverride) Render ¶ added in v0.8.3
func (c *CompiledOverride) Render(d Data, orgName string) (msg Message, removed []string, err error)
Render renders the override. Every value is escaped (HTML for the HTML part), and the HTML that results passes the allowlist sanitizer; removed says what it dropped.
type Data ¶
type Data struct {
Realm string
User User
Link string
ExpiresAt time.Time
Data map[string]any
// App is what the realm's email.data_hook returned (the company's name, colours, logo...):
// nil when there is no hook, or it failed. Templates that use it guard with {{if .App}}.
App map[string]any
}
Data is what a template can use: {{.Realm}}, {{.User.Email}}, {{.User.Locale}}, {{.Link}}, {{.ExpiresAt}}, {{.Data}} and {{.App}}. Templates never receive text written by whoever caused the email, so backd can't be used to send someone else's words.
func SampleData ¶ added in v0.8.3
SampleData is what a template of kind is checked and previewed with.
type Mustache ¶ added in v0.8.3
type Mustache struct {
// contains filtered or unexported fields
}
Mustache is a parsed owner template.
func (*Mustache) Prints ¶ added in v0.8.3
Prints reports whether the template prints name (not only tests it).
type Override ¶ added in v0.8.3
Override is a company's own template of one kind in one language (design/team-access.md §11): the three parts of a message, in the logic-less syntax of ParseMustache.
type OverrideProblem ¶ added in v0.8.3
type OverrideProblem struct {
Part string // subject, text or html
Line, Column int // 0 when the problem isn't at a place
Message string
}
OverrideProblem is one thing wrong with an override, for the person who wrote it.
func (OverrideProblem) String ¶ added in v0.8.3
func (p OverrideProblem) String() string
type PageData ¶
type PageData struct {
Realm string
Locale string
Action string
Token string
Error string
// ErrorCode names the problem in Error (password_mismatch, password_policy)
// so a template can write it in its own words.
ErrorCode string
Kind string
// Email is the address the link is about: the new one for a change, the
// previous one for an undo, the invited one for an invitation.
Email string
// Redirect and BackURL are checked against the realm's allowed_redirects
// before they get here, so they may use an app's own scheme (acme://),
// which html/template would otherwise refuse in a link.
Redirect htmltemplate.URL
DelaySeconds int
BackURL htmltemplate.URL
// App is what the realm's pages.data_hook returned for this page ({{.App.name}}): nil when
// there is no hook, or it failed. Pages that use it guard with {{if .App}}.
App map[string]any
}
PageData is what a page template can use: {{.Realm}}, {{.Locale}}, {{.Action}} (where its form posts), {{.Token}} (the hidden field), {{.Error}} and {{.ErrorCode}} (what to show on the form again), {{.Kind}} (which flow a result is for), {{.Email}} (the address the link is about), {{.Redirect}} and {{.DelaySeconds}} (where the result sends the user and when), {{.BackURL}} (a way back to the app on the error page).
type Pages ¶
type Pages struct {
// contains filtered or unexported fields
}
Pages are a realm's parsed hosted pages, held in memory.
func LoadPages ¶
LoadPages reads <realm>/pages/ (dir): <kind>/<locale>.html for every kind of PageKinds and every one of locales. It parses and test-renders each page, and reports every problem together, naming the files.
type Purpose ¶
type Purpose string
Purpose is what a token created for an email can be redeemed for.
func TokenPurpose ¶
TokenPurpose returns the token an email of this kind carries, or "" when the kind has no link with a token.
func (Purpose) DefaultLifetime ¶
DefaultLifetime is how long a token of this purpose can be redeemed.
type SyntaxError ¶ added in v0.8.3
SyntaxError is a problem in an owner template, with where it is.
func (*SyntaxError) Error ¶ added in v0.8.3
func (e *SyntaxError) Error() string
type Templates ¶
type Templates struct {
// contains filtered or unexported fields
}
Templates are a realm's parsed templates, held in memory.
func Load ¶
Load reads <realm>/email/ (dir): one folder per kind, with <locale>.subject.txt, <locale>.txt and <locale>.html per locale. It parses and test-renders every template, requires the RequiredKinds, and requires every kind found to be complete in every one of locales. Problems are reported together, naming the files.