Files
base/core/system_alert.go
zeekay e7df8c856e base: complete pocketbase→base rename; Base = pure IAM client
Rename / namespace:
- Finish github.com/pocketbase/* → github.com/hanzoai/base (+ /dbx) sweep
  across all Go sources; restore the renamed root launcher (Base, base.go).
- Drop the upstream-only surfaces ours doesn't ship (admin ui/, the
  password/OTP/oauth2/impersonate auth files, extensions) re-added by the
  upstream merge.
- Decomplect the on-disk layout to one base_/ namespace: data/, migrations/,
  hooks/, types.d.ts are siblings under base_/ (default data dir
  base_/data). Drop the hz_ prefix everywhere; collapse migratecmd's
  identical JS/Go branches to one path.
- Remove the accidentally-committed hz_data/ dev dir (db + iam.key) and
  gitignore the runtime namespace dirs.

Deps / build:
- kmsclient now imported from the light github.com/hanzoai/kms/sdk/go module
  (the root kms module zip exceeds Go's download limit); cloud/pkg/base
  Mount removed (lives in the fused daemon).
- replicate v0.4.0 → v0.8.0; metric.Counter.Get() (no prometheus dto);
  network/metrics.go import fix; auth gitlab/github tidy.

Tests / behavior:
- Wire registerNotifyWatcherHooks; debounce notify files past the kqueue
  rescan window so cross-instance settings/collections reload fires.
- migratecmd: exclude createdAt/updatedAt (not created/updated); regenerate
  Go+JS automigration goldens from canonical output.
- Convert the two stale archive red-probes to assert the landed defenses
  (backlog cap bounds memory; forged-segment signature is rejected).

IAM-native (Base = pure IAM client):
- Rip the ~1k-line home-grown embedded IAM (iam_embedded*.go) and the
  iam-user CLI. Base no longer reinvents identity as _iam_users/_superusers
  rows. IAM_ENDPOINT (hanzo.id, or an in-process iam.Embed in the fused
  daemon) is required; Base validates IAM JWTs via JWKS only — superuser is
  the IAM admin claim, resolved to an ephemeral unpersisted record.
- Extract the IDV reverse-proxy (/v1/idv/*) into idv_proxy.go — it was
  mis-colocated in the embedded onboarding file and is independent of IAM.
- Bind the SQL console (/v1/sql) and test it through real IAM JWTs against a
  JWKS stub (no hardcoded record tokens, no persisted rows).
2026-06-10 21:29:14 -07:00

132 lines
3.3 KiB
Go

package core
import (
"bytes"
"errors"
"html"
"html/template"
"net/mail"
"github.com/hanzoai/base/tools/mailer"
)
const systemAlertHTML = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
body, html {
padding: 0;
margin: 0;
border: 0;
color: #16161a;
background: #fff;
font-size: 14px;
line-height: 20px;
font-weight: normal;
font-family: Source Sans Pro, sans-serif, emoji;
}
body {
padding: 20px 30px;
}
p {
display: block;
margin: 10px 0;
font-family: inherit;
}
small {
font-size: 12px;
line-height: 16px;
}
strong {
font-weight: bold;
}
em, i {
font-style: italic;
}
a {
color: inherit;
}
.alert {
padding: 15px;
background: #e4e8ec;
border-radius: 5px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<p>{{.AppName}} system alert occurred:</p>
<p class="alert"><strong>{{.AlertDetails}}</strong></p>
<p>For more information you could explore the logs in the dashboard of your application.</p>
</body>
</html>`
// sendSystemAlertToAllSuperusers sends a system error alert to all superusers.
//
// note: unexported for now until there is clarity around the planned log level alerts.
func sendSystemAlertToAllSuperusers(app App, subject string, details string) error {
superusers, err := app.FindAllRecords(CollectionNameSuperusers)
if err != nil {
return err
}
var alertErrors []error
for _, superuser := range superusers {
err := sendSystemAlert(app, superuser, subject, details)
if err != nil {
alertErrors = append(alertErrors, err)
}
}
return errors.Join(alertErrors...)
}
// sendSystemAlert sends a system error alert to a single superuser.
//
// note: unexported for now until there is clarity around the planned log level alerts.
func sendSystemAlert(app App, superuser *Record, subject string, details string) error {
if !superuser.IsSuperuser() {
return errors.New("system alerts can be sent only to superusers")
}
if subject == "" || details == "" {
return errors.New("system alerts subject and details are required")
}
data := struct {
AppName string
AlertDetails string
}{
AppName: app.Settings().Meta.AppName,
AlertDetails: details,
}
tpl := template.New("system_alert")
var parseErr error
tpl, parseErr = tpl.Parse(systemAlertHTML)
if parseErr != nil {
return parseErr
}
var buff bytes.Buffer
executeErr := tpl.Execute(&buff, data)
if executeErr != nil {
return executeErr
}
message := &mailer.Message{
From: mail.Address{
Name: app.Settings().Meta.SenderName,
Address: app.Settings().Meta.SenderAddress,
},
To: []mail.Address{{Address: superuser.Email()}},
Subject: "[" + app.Settings().Meta.AppName + " system alert] " + html.EscapeString(subject),
HTML: buff.String(),
}
return app.NewMailClient().Send(message)
}