@@ -278,6 +278,60 @@ If this wasn't you, sign in immediately, delete the key from |
| 278 | 278 | }, nil |
| 279 | 279 | } |
| 280 | 280 | |
| 281 | +// GPGKeyAddedMessage builds the new-GPG-key notification email. Sent |
| 282 | +// on successful key add — name/fingerprint/IP help the user spot a |
| 283 | +// compromise. Mirrors SSHKeyAddedMessage; the only visible difference |
| 284 | +// in the body is the wording ("GPG key" vs "SSH key") and the |
| 285 | +// settings link target. |
| 286 | +// |
| 287 | +// The display "name" comes from the user-supplied title field; when |
| 288 | +// the user omits it (gh allows blank), we fall back to a generic |
| 289 | +// "(no title)" label so the email body stays readable. |
| 290 | +func GPGKeyAddedMessage(b Branding, to, username, name, fingerprint, ip string) (Message, error) { |
| 291 | + if strings.TrimSpace(name) == "" { |
| 292 | + name = "(no title)" |
| 293 | + } |
| 294 | + const text = `Hi {{.Username}}, |
| 295 | + |
| 296 | +A new GPG key was added to your {{.SiteName}} account. |
| 297 | + |
| 298 | + Name: {{.Name}} |
| 299 | + Fingerprint: {{.Fingerprint}} |
| 300 | +{{if .IP}} IP: {{.IP}} |
| 301 | +{{end}} |
| 302 | +If this wasn't you, sign in immediately, delete the key from |
| 303 | +{{.BaseURL}}/settings/keys, and reset your password.` |
| 304 | + |
| 305 | + const html = `<p>Hi <strong>{{.Username}}</strong>,</p> |
| 306 | +<p>A new GPG key was added to your {{.SiteName}} account.</p> |
| 307 | +<ul> |
| 308 | + <li><strong>Name:</strong> {{.Name}}</li> |
| 309 | + <li><strong>Fingerprint:</strong> <code>{{.Fingerprint}}</code></li> |
| 310 | + {{if .IP}}<li><strong>IP:</strong> {{.IP}}</li>{{end}} |
| 311 | +</ul> |
| 312 | +<p>If this wasn't you, sign in immediately, delete the key from |
| 313 | +<a href="{{.BaseURL}}/settings/keys">your SSH and GPG keys settings</a>, and reset your password.</p>` |
| 314 | + |
| 315 | + data := struct{ SiteName, BaseURL, Username, Name, Fingerprint, IP string }{ |
| 316 | + b.SiteName, b.BaseURL, username, name, fingerprint, ip, |
| 317 | + } |
| 318 | + txt, err := renderText(textTemplate.Must(textTemplate.New("gpg_added.txt").Parse(text)), data) |
| 319 | + if err != nil { |
| 320 | + return Message{}, err |
| 321 | + } |
| 322 | + htmlBody, err := renderHTML(template.Must(template.New("gpg_added.html").Parse(html)), data) |
| 323 | + if err != nil { |
| 324 | + return Message{}, err |
| 325 | + } |
| 326 | + return Message{ |
| 327 | + From: b.From, |
| 328 | + To: to, |
| 329 | + Subject: fmt.Sprintf("New GPG key added to your %s account", b.SiteName), |
| 330 | + Text: txt, |
| 331 | + HTML: htmlBody, |
| 332 | + }, nil |
| 333 | +} |
| 334 | + |
| 281 | 335 | // NoticeMessage builds a 2FA / security state-change notice for kind. The |
| 282 | 336 | // kind names match audit-log Action values where applicable. |
| 283 | 337 | func NoticeMessage(b Branding, to, username, kind string) (Message, error) { |