Base sends no auth mail, because it has no auth flow to send it for

The verification and email-change templates were the last of local auth. Their
endpoints went in the IAM-native rip — bindRecordAuthApi keeps auth-methods and
auth-refresh and nothing else — so both answered 404 for every deployment, not
only IAM ones, and their buttons addressed a route in an admin SPA that no
longer exists. Re-pathing them last commit made them honest; this removes them,
along with everything that existed only to serve them.

That turned out to be a lot, and each piece was already dead on its own terms:

  core.EmailTemplate and the two collection fields — no other user
  the mails package entire — its two functions were the only callers of its
    own template resolver and its own html layout
  the $mails JSVM namespace — an empty object once the two binds went, and its
    type declarations advertised FIVE helpers, three of which (password reset,
    OTP, auth alert) were removed in that same rip and never bound at all, so
    hook authors have been reading a contract for functions that do not exist
  OnMailerRecordVerificationSend / OnMailerRecordEmailChangeSend — nothing has
    been able to fire either since the mails package was their only caller
  core.MailerRecordEvent — the type those two hooks carried

Nothing loses a capability. $app.newMailClient().send() is the general sender,
and it is what an app binding its own confirm route would have used anyway; the
two removed binds were convenience wrappers over it.

POST /v1/settings/test/email stays and gets better. It rendered a template
against a throwaway record, so "can this deployment deliver mail" was answerable
only through a flow that no longer exists. It sends a plain message now — no
template, no collection, no record, no token — which is the question an operator
has after editing SMTP settings.

The collection fields are DEFAULTS applied at creation; a collection stores its
own copy, and an options key the struct no longer declares is ignored on read,
so nothing already deployed changes shape. types.d.ts is regenerated by its own
generator rather than hand-edited (the generator still listed the deleted
package, which is why it had to be fixed first).
This commit is contained in:
2026-08-10 21:14:46 -07:00
parent ace1fd65c2
commit e9634a77dd
20 changed files with 3916 additions and 4293 deletions
+21
View File
@@ -342,6 +342,27 @@ Store keys: `StoreKeyExternalAuthOnly` (always true once platform
registers), `StoreKeyJWKSURL` (external mode), `StoreKeyAuthUsersCollection`
(default `"users"`).
## Base sends no auth mail
There is no verification or email-change mail, no template for either on a
collection, and no `mails` package. The endpoints those mails pointed at were
deleted in the IAM-native rip — `bindRecordAuthApi` keeps `auth-methods` and
`auth-refresh` and nothing else — so the templates described flows that answer
404 for every deployment, and their buttons addressed a route in an admin SPA
that no longer exists.
What went with them, because each existed only to serve those two mails:
`core.EmailTemplate` and its two collection fields, the `$mails` JSVM namespace
(whose type declarations advertised five helpers, three of which were removed in
that same rip and never bound), the `OnMailerRecordVerificationSend` and
`OnMailerRecordEmailChangeSend` hooks — which nothing could fire — and
`core.MailerRecordEvent`.
Sending mail is untouched: `$app.newMailClient().send()` is the general sender an
app's own hook uses, SMTP settings stay, and `POST /v1/settings/test/email` still
proves they work — it sends a plain message now rather than rendering a template
for a flow that is gone, which is the question an operator actually has.
## Mount prefix (`BASE_API_PREFIX`)
One knob for where the app's data plane lives. Default `/v1`. For
+12 -51
View File
@@ -364,16 +364,14 @@ func TestSettingsTestEmail(t *testing.T) {
ExpectedStatus: 400,
ExpectedContent: []string{
`"email":{"code":"validation_required"`,
`"template":{"code":"validation_required"`,
},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as superuser (verifiation template)",
Name: "authorized as superuser",
Method: http.MethodPost,
URL: "/v1/settings/test/email",
Body: strings.NewReader(`{
"template": "verification",
"email": "[email protected]"
}`),
Headers: map[string]string{
@@ -381,63 +379,26 @@ func TestSettingsTestEmail(t *testing.T) {
},
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
if app.TestMailer.TotalSend() != 1 {
t.Fatalf("[verification] Expected 1 sent email, got %d", app.TestMailer.TotalSend())
t.Fatalf("Expected 1 sent email, got %d", app.TestMailer.TotalSend())
}
if len(app.TestMailer.LastMessage().To) != 1 {
t.Fatalf("[verification] Expected 1 recipient, got %v", app.TestMailer.LastMessage().To)
msg := app.TestMailer.LastMessage()
if len(msg.To) != 1 || msg.To[0].Address != "[email protected]" {
t.Fatalf("Expected the email sent to [email protected], got %v", msg.To)
}
if app.TestMailer.LastMessage().To[0].Address != "[email protected]" {
t.Fatalf("[verification] Expected the email to be sent to %s, got %s", "[email protected]", app.TestMailer.LastMessage().To[0].Address)
}
if !strings.Contains(app.TestMailer.LastMessage().HTML, "Verify") {
t.Fatalf("[verification] Expected to sent a verification email, got \n%v\n%v", app.TestMailer.LastMessage().Subject, app.TestMailer.LastMessage().HTML)
// The send proves SMTP reaches the address and nothing else —
// there is no flow behind it to complete, so no token and no link.
if !strings.Contains(msg.HTML, "SMTP settings work") {
t.Fatalf("Expected the email to state what it proves, got \n%v\n%v", msg.Subject, msg.HTML)
}
},
ExpectedStatus: 204,
ExpectedContent: []string{},
ExpectedEvents: map[string]int{
"*": 0,
"OnMailerSend": 1,
"OnMailerRecordVerificationSend": 1,
},
},
{
Name: "authorized as superuser (email change)",
Method: http.MethodPost,
URL: "/v1/settings/test/email",
Body: strings.NewReader(`{
"template": "email-change",
"email": "[email protected]"
}`),
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
},
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
if app.TestMailer.TotalSend() != 1 {
t.Fatalf("[email-change] Expected 1 sent email, got %d", app.TestMailer.TotalSend())
}
if len(app.TestMailer.LastMessage().To) != 1 {
t.Fatalf("[email-change] Expected 1 recipient, got %v", app.TestMailer.LastMessage().To)
}
if app.TestMailer.LastMessage().To[0].Address != "[email protected]" {
t.Fatalf("[email-change] Expected the email to be sent to %s, got %s", "[email protected]", app.TestMailer.LastMessage().To[0].Address)
}
if !strings.Contains(app.TestMailer.LastMessage().HTML, "Confirm new email") {
t.Fatalf("[email-change] Expected to sent a confirm new email email, got \n%v\n%v", app.TestMailer.LastMessage().Subject, app.TestMailer.LastMessage().HTML)
}
},
ExpectedStatus: 204,
ExpectedContent: []string{},
ExpectedEvents: map[string]int{
"*": 0,
"OnMailerSend": 1,
"OnMailerRecordEmailChangeSend": 1,
"*": 0,
"OnMailerSend": 1,
},
},
}
-18
View File
@@ -1167,24 +1167,6 @@ type App interface {
// It allows intercepting the email message or to use a custom mailer client.
OnMailerSend() *hook.Hook[*MailerEvent]
// OnMailerBeforeRecordVerificationSend hook is triggered when
// sending a verification email to an auth record, allowing
// you to intercept and customize the email message that is being sent.
//
// If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags.
OnMailerRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent]
// OnMailerRecordEmailChangeSend hook is triggered when sending a
// confirmation new address email to an auth record, allowing
// you to intercept and customize the email message that is being sent.
//
// If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags.
OnMailerRecordEmailChangeSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent]
// ---------------------------------------------------------------
// Realtime API event hooks
// ---------------------------------------------------------------
-12
View File
@@ -165,8 +165,6 @@ type BaseApp struct {
// mailer event hooks
onMailerSend *hook.Hook[*MailerEvent]
onMailerRecordVerificationSend *hook.Hook[*MailerRecordEvent]
onMailerRecordEmailChangeSend *hook.Hook[*MailerRecordEvent]
// realtime api event hooks
onRealtimeConnectRequest *hook.Hook[*RealtimeConnectRequestEvent]
@@ -348,8 +346,6 @@ func (app *BaseApp) initHooks() {
// mailer event hooks
app.onMailerSend = &hook.Hook[*MailerEvent]{}
app.onMailerRecordVerificationSend = &hook.Hook[*MailerRecordEvent]{}
app.onMailerRecordEmailChangeSend = &hook.Hook[*MailerRecordEvent]{}
// realtime API event hooks
app.onRealtimeConnectRequest = &hook.Hook[*RealtimeConnectRequestEvent]{}
@@ -1113,14 +1109,6 @@ func (app *BaseApp) OnMailerSend() *hook.Hook[*MailerEvent] {
return app.onMailerSend
}
func (app *BaseApp) OnMailerRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] {
return hook.NewTaggedHook(app.onMailerRecordVerificationSend, tags...)
}
func (app *BaseApp) OnMailerRecordEmailChangeSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] {
return hook.NewTaggedHook(app.onMailerRecordEmailChangeSend, tags...)
}
// -------------------------------------------------------------------
// Realtime API event hooks
// -------------------------------------------------------------------
-6
View File
@@ -45,8 +45,6 @@ func (m *Collection) unsetMissingOAuth2MappedFields() {
func (m *Collection) setDefaultAuthOptions() {
m.collectionAuthOptions = collectionAuthOptions{
VerificationTemplate: defaultVerificationTemplate,
ConfirmEmailChangeTemplate: defaultConfirmEmailChangeTemplate,
AuthRule: types.Pointer(""),
AuthToken: TokenConfig{
Secret: security.RandomString(50),
@@ -112,8 +110,6 @@ type collectionAuthOptions struct {
// Default email templates
// ---
VerificationTemplate EmailTemplate `form:"verificationTemplate" json:"verificationTemplate"`
ConfirmEmailChangeTemplate EmailTemplate `form:"confirmEmailChangeTemplate" json:"confirmEmailChangeTemplate"`
}
func (o *collectionAuthOptions) validate(cv *collectionValidator) error {
@@ -134,8 +130,6 @@ func (o *collectionAuthOptions) validate(cv *collectionValidator) error {
validation.Field(&o.EmailChangeToken),
validation.Field(&o.VerificationToken),
validation.Field(&o.FileToken),
validation.Field(&o.VerificationTemplate, validation.Required),
validation.Field(&o.ConfirmEmailChangeTemplate, validation.Required),
)
if err != nil {
return err
@@ -151,26 +151,6 @@ func TestCollectionAuthOptionsValidate(t *testing.T) {
},
expectedErrors: []string{"fileToken"},
},
// templates
{
name: "trigger verificationTemplate validations",
collection: func(app core.App) (*core.Collection, error) {
c := core.NewAuthCollection("new_auth")
c.VerificationTemplate.Body = ""
return c, nil
},
expectedErrors: []string{"verificationTemplate"},
},
{
name: "trigger confirmEmailChangeTemplate validations",
collection: func(app core.App) (*core.Collection, error) {
c := core.NewAuthCollection("new_auth")
c.ConfirmEmailChangeTemplate.Body = ""
return c, nil
},
expectedErrors: []string{"confirmEmailChangeTemplate"},
},
}
for _, s := range scenarios {
+1 -3
View File
@@ -658,7 +658,6 @@ func TestCollectionSerialize(t *testing.T) {
`"type":"base"`,
},
[]string{
"verificationTemplate",
"manageRule",
"authRule",
"secret",
@@ -687,7 +686,6 @@ func TestCollectionSerialize(t *testing.T) {
`"viewQuery":"1=1"`,
},
[]string{
"verificationTemplate",
"manageRule",
"authRule",
"secret",
@@ -783,7 +781,7 @@ func TestCollectionDBExport(t *testing.T) {
},
{
core.CollectionTypeAuth,
`{"createRule":"1=3","created":"2024-07-01 01:02:03.456Z","deleteRule":"1=5","fields":[{"hidden":false,"id":"f1_id","name":"f1","presentable":false,"required":false,"system":true,"type":"bool"},{"hidden":false,"id":"f2_id","name":"f2","presentable":false,"required":true,"system":false,"type":"bool"}],"id":"test_id","indexes":["CREATE INDEX idx1 on test_name(id)","CREATE INDEX idx2 on test_name(id)"],"listRule":"1=1","name":"test_name","options":{"authRule":null,"manageRule":"1=6","oauth2":{"providers":null,"mappedFields":{"id":"","name":"","username":"","avatarURL":""},"enabled":false},"authToken":{"duration":0},"emailChangeToken":{"duration":0},"verificationToken":{"duration":0},"fileToken":{"duration":0},"verificationTemplate":{"subject":"","body":""},"confirmEmailChangeTemplate":{"subject":"","body":""}},"system":true,"type":"auth","updateRule":"1=4","updated":"2024-07-01 01:02:03.456Z","viewRule":"1=7"}`,
`{"createRule":"1=3","created":"2024-07-01 01:02:03.456Z","deleteRule":"1=5","fields":[{"hidden":false,"id":"f1_id","name":"f1","presentable":false,"required":false,"system":true,"type":"bool"},{"hidden":false,"id":"f2_id","name":"f2","presentable":false,"required":true,"system":false,"type":"bool"}],"id":"test_id","indexes":["CREATE INDEX idx1 on test_name(id)","CREATE INDEX idx2 on test_name(id)"],"listRule":"1=1","name":"test_name","options":{"authRule":null,"manageRule":"1=6","oauth2":{"providers":null,"mappedFields":{"id":"","name":"","username":"","avatarURL":""},"enabled":false},"authToken":{"duration":0},"emailChangeToken":{"duration":0},"verificationToken":{"duration":0},"fileToken":{"duration":0}},"system":true,"type":"auth","updateRule":"1=4","updated":"2024-07-01 01:02:03.456Z","viewRule":"1=7"}`,
},
}
-6
View File
@@ -163,12 +163,6 @@ type MailerEvent struct {
Message *mailer.Message
}
type MailerRecordEvent struct {
MailerEvent
baseRecordEventData
Meta map[string]any
}
// -------------------------------------------------------------------
// Model events data
// -------------------------------------------------------------------
+21 -64
View File
@@ -1,26 +1,27 @@
package forms
import (
"errors"
"net/mail"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/mails"
"github.com/hanzoai/base/tools/mailer"
)
const (
TestTemplateVerification = "verification"
TestTemplateEmailChange = "email-change"
)
// TestEmailSend is a email template test request form.
// TestEmailSend proves the configured SMTP settings can deliver a message.
//
// It used to render one of the auth-record email templates against a throwaway
// record, which made "can this deployment send mail" answerable only through a
// flow this fork no longer has: verification and email-change were removed with
// the rest of local auth, so both branches described pages that do not exist and
// endpoints that answer 404. The remaining question is the one an operator
// actually has after editing SMTP settings, and it needs no template, no
// collection and no record — just a message that either arrives or does not.
type TestEmailSend struct {
app core.App
Email string `form:"email" json:"email"`
Template string `form:"template" json:"template"`
Collection string `form:"collection" json:"collection"` // optional, fallbacks to _superusers
Email string `form:"email" json:"email"`
}
// NewTestEmailSend creates and initializes new TestEmailSend form.
@@ -31,74 +32,30 @@ func NewTestEmailSend(app core.App) *TestEmailSend {
// Validate makes the form validatable by implementing [validation.Validatable] interface.
func (form *TestEmailSend) Validate() error {
return validation.ValidateStruct(form,
validation.Field(
&form.Collection,
validation.Length(1, 255),
validation.By(form.checkAuthCollection),
),
validation.Field(
&form.Email,
validation.Required,
validation.Length(1, 255),
is.EmailFormat,
),
validation.Field(
&form.Template,
validation.Required,
validation.In(
TestTemplateVerification,
TestTemplateEmailChange,
),
),
)
}
func (form *TestEmailSend) checkAuthCollection(value any) error {
v, _ := value.(string)
if v == "" {
return nil // nothing to check
}
c, _ := form.app.FindCollectionByNameOrId(v)
if c == nil || !c.IsAuth() {
return validation.NewError("validation_invalid_auth_collection", "Must be a valid auth collection id or name.")
}
return nil
}
// Submit validates and sends a test email to the form.Email address.
func (form *TestEmailSend) Submit() error {
if err := form.Validate(); err != nil {
return err
}
collectionIdOrName := form.Collection
if collectionIdOrName == "" {
collectionIdOrName = core.CollectionNameSuperusers
settings := form.app.Settings()
name := settings.Meta.AppName
message := &mailer.Message{
From: mail.Address{Name: settings.Meta.SenderName, Address: settings.Meta.SenderAddress},
To: []mail.Address{{Address: form.Email}},
Subject: name + " test email",
HTML: "<p>This is a test message from " + name + ". Your SMTP settings work.</p>",
}
collection, err := form.app.FindCollectionByNameOrId(collectionIdOrName)
if err != nil {
return err
}
record := core.NewRecord(collection)
for _, field := range collection.Fields {
if field.GetHidden() {
continue
}
record.Set(field.GetName(), "__test_"+field.GetName()+"__")
}
record.RefreshTokenKey()
record.SetEmail(form.Email)
switch form.Template {
case TestTemplateVerification:
return mails.SendRecordVerification(form.app, record)
case TestTemplateEmailChange:
return mails.SendRecordChangeEmail(form.app, record, form.Email)
default:
return errors.New("unknown template " + form.Template)
}
return form.app.NewMailClient().Send(message)
}
+13 -23
View File
@@ -14,29 +14,22 @@ func TestEmailSendValidateAndSubmit(t *testing.T) {
t.Parallel()
scenarios := []struct {
template string
email string
collection string
expectedErrors []string
}{
{"", "", "", []string{"template", "email"}},
{"invalid", "[email protected]", "", []string{"template"}},
{forms.TestTemplateVerification, "invalid", "", []string{"email"}},
{forms.TestTemplateVerification, "[email protected]", "invalid", []string{"collection"}},
{forms.TestTemplateVerification, "[email protected]", "demo1", []string{"collection"}},
{forms.TestTemplateVerification, "[email protected]", "users", nil},
{forms.TestTemplateEmailChange, "[email protected]", "", nil},
{"", []string{"email"}},
{"invalid", []string{"email"}},
{strings.Repeat("a", 250) + "@example.com", []string{"email"}},
{"[email protected]", nil},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%s", i, s.template), func(t *testing.T) {
t.Run(fmt.Sprintf("%d_%s", i, s.email), func(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
form := forms.NewTestEmailSend(app)
form.Email = s.email
form.Template = s.template
form.Collection = s.collection
result := form.Submit()
@@ -69,18 +62,15 @@ func TestEmailSendValidateAndSubmit(t *testing.T) {
return
}
var expectedContent string
switch s.template {
case forms.TestTemplateEmailChange:
expectedContent = "Confirm new email"
case forms.TestTemplateVerification:
expectedContent = "Verify"
default:
expectedContent = "__UNKNOWN_TEMPLATE__"
// The point of the send is that SMTP works, so the message says so
// and carries no token, no collection and no link — there is no flow
// behind it to complete.
msg := app.TestMailer.LastMessage()
if !strings.Contains(msg.HTML, "SMTP settings work") {
t.Errorf("Expected the email to state what it proves, got\n%v", msg.HTML)
}
if !strings.Contains(app.TestMailer.LastMessage().HTML, expectedContent) {
t.Errorf("Expected the email to contains %q, got\n%v", expectedContent, app.TestMailer.LastMessage().HTML)
if len(msg.To) != 1 || msg.To[0].Address != s.email {
t.Errorf("Expected the email addressed to %q, got %v", s.email, msg.To)
}
})
}
-9
View File
@@ -22,7 +22,6 @@ import (
"github.com/hanzoai/base/apis"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/forms"
"github.com/hanzoai/base/mails"
"github.com/hanzoai/base/tools/filesystem"
"github.com/hanzoai/base/tools/hook"
"github.com/hanzoai/base/tools/inflector"
@@ -676,14 +675,6 @@ func dbxBinds(vm *goja.Runtime) {
obj.Set("notBetween", dbx.NotBetween)
}
func mailsBinds(vm *goja.Runtime) {
obj := vm.NewObject()
vm.Set("$mails", obj)
obj.Set("sendRecordVerification", mails.SendRecordVerification)
obj.Set("sendRecordChangeEmail", mails.SendRecordChangeEmail)
}
func securityBinds(vm *goja.Runtime) {
obj := vm.NewObject()
vm.Set("$security", obj)
+1 -39
View File
@@ -788,44 +788,6 @@ func TestDbxBinds(t *testing.T) {
}
}
func TestMailsBindsCount(t *testing.T) {
vm := goja.New()
mailsBinds(vm)
testBindsCount(vm, "$mails", 2, t)
}
func TestMailsBinds(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
record, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
vm := goja.New()
baseBinds(vm)
mailsBinds(vm)
vm.Set("$app", app)
vm.Set("record", record)
_, vmErr := vm.RunString(`
$mails.sendRecordVerification($app, record);
if (!$app.testMailer.lastMessage().html.includes("/_/#/auth/confirm-verification/")) {
throw new Error("Expected record verification email, got:" + JSON.stringify($app.testMailer.lastMessage()))
}
$mails.sendRecordChangeEmail($app, record, "[email protected]");
if (!$app.testMailer.lastMessage().html.includes("/_/#/auth/confirm-email-change/")) {
throw new Error("Expected record email change email, got:" + JSON.stringify($app.testMailer.lastMessage()))
}
`)
if vmErr != nil {
t.Fatal(vmErr)
}
}
func TestSecurityBindsCount(t *testing.T) {
vm := goja.New()
securityBinds(vm)
@@ -1594,7 +1556,7 @@ func TestHooksBindsCount(t *testing.T) {
vm := goja.New()
hooksBinds(app, vm, nil)
testBindsCount(vm, "this", 74, t)
testBindsCount(vm, "this", 72, t)
}
func TestHooksBinds(t *testing.T) {
File diff suppressed because it is too large Load Diff
-19
View File
@@ -847,24 +847,6 @@ declare namespace $dbx {
export let notBetween: dbx.notBetween
}
// -------------------------------------------------------------------
// mailsBinds
// -------------------------------------------------------------------
/**
* ` + "`" + `$mails` + "`" + ` defines helpers to send common
* auth records emails like verification, password reset, etc.
*
* @group Base
*/
declare namespace $mails {
let sendRecordPasswordReset: mails.sendRecordPasswordReset
let sendRecordVerification: mails.sendRecordVerification
let sendRecordChangeEmail: mails.sendRecordChangeEmail
let sendRecordOTP: mails.sendRecordOTP
let sendRecordAuthAlert: mails.sendRecordAuthAlert
}
// -------------------------------------------------------------------
// securityBinds
// -------------------------------------------------------------------
@@ -1276,7 +1258,6 @@ func main() {
"github.com/hanzoai/base/tools/security": {"*"},
"github.com/hanzoai/base/tools/filesystem": {"*"},
"github.com/hanzoai/base/tools/template": {"*"},
"github.com/hanzoai/base/mails": {"*"},
"github.com/hanzoai/base/apis": {"*"},
"github.com/hanzoai/base/core": {"*"},
"github.com/hanzoai/base/forms": {"*"},
-2
View File
@@ -208,7 +208,6 @@ func (p *plugin) registerMigrations() error {
httpClientBinds(vm)
filesystemBinds(vm)
formsBinds(vm)
mailsBinds(vm)
vm.Set("$template", templateRegistry)
vm.Set("__hooks", absHooksDir)
@@ -297,7 +296,6 @@ func (p *plugin) registerHooks() error {
httpClientBinds(vm)
formsBinds(vm)
apisBinds(vm)
mailsBinds(vm)
vm.Set("$app", p.app)
vm.Set("$template", templateRegistry)
-32
View File
@@ -31,10 +31,6 @@ migrate((app) => {
"authToken": {
"duration": 604800
},
"confirmEmailChangeTemplate": {
"body": "<p>Hello,</p>\n<p>A new email address was requested for your {APP_NAME} account.</p>\n<p>Open your account to confirm it.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Confirm your {APP_NAME} new email address"
},
"createRule": null,
"deleteRule": null,
"emailChangeToken": {
@@ -123,10 +119,6 @@ migrate((app) => {
"system": true,
"type": "auth",
"updateRule": null,
"verificationTemplate": {
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Your email address is confirmed through your {APP_NAME} account.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Verify your {APP_NAME} email"
},
"verificationToken": {
"duration": 259200
},
@@ -160,10 +152,6 @@ func init() {
"authToken": {
"duration": 604800
},
"confirmEmailChangeTemplate": {
"body": "<p>Hello,</p>\n<p>A new email address was requested for your {APP_NAME} account.</p>\n<p>Open your account to confirm it.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Confirm your {APP_NAME} new email address"
},
"createRule": null,
"deleteRule": null,
"emailChangeToken": {
@@ -252,10 +240,6 @@ func init() {
"system": true,
"type": "auth",
"updateRule": null,
"verificationTemplate": {
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Your email address is confirmed through your {APP_NAME} account.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Verify your {APP_NAME} email"
},
"verificationToken": {
"duration": 259200
},
@@ -377,10 +361,6 @@ migrate((app) => {
"authToken": {
"duration": 604800
},
"confirmEmailChangeTemplate": {
"body": "<p>Hello,</p>\n<p>A new email address was requested for your {APP_NAME} account.</p>\n<p>Open your account to confirm it.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Confirm your {APP_NAME} new email address"
},
"createRule": null,
"deleteRule": null,
"emailChangeToken": {
@@ -469,10 +449,6 @@ migrate((app) => {
"system": false,
"type": "auth",
"updateRule": null,
"verificationTemplate": {
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Your email address is confirmed through your {APP_NAME} account.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Verify your {APP_NAME} email"
},
"verificationToken": {
"duration": 259200
},
@@ -509,10 +485,6 @@ func init() {
"authToken": {
"duration": 604800
},
"confirmEmailChangeTemplate": {
"body": "<p>Hello,</p>\n<p>A new email address was requested for your {APP_NAME} account.</p>\n<p>Open your account to confirm it.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Confirm your {APP_NAME} new email address"
},
"createRule": null,
"deleteRule": null,
"emailChangeToken": {
@@ -601,10 +573,6 @@ func init() {
"system": false,
"type": "auth",
"updateRule": null,
"verificationTemplate": {
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Your email address is confirmed through your {APP_NAME} account.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}\" target=\"_blank\" rel=\"noopener\">Open {APP_NAME}</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
"subject": "Verify your {APP_NAME} email"
},
"verificationToken": {
"duration": 259200
},
-16
View File
@@ -509,22 +509,6 @@ func NewTestAppWithConfig(config core.BaseAppConfig) (*TestApp, error) {
Priority: -99999,
})
t.OnMailerRecordVerificationSend().Bind(&hook.Handler[*core.MailerRecordEvent]{
Func: func(e *core.MailerRecordEvent) error {
t.registerEventCall("OnMailerRecordVerificationSend")
return e.Next()
},
Priority: -99999,
})
t.OnMailerRecordEmailChangeSend().Bind(&hook.Handler[*core.MailerRecordEvent]{
Func: func(e *core.MailerRecordEvent) error {
t.registerEventCall("OnMailerRecordEmailChangeSend")
return e.Next()
},
Priority: -99999,
})
t.OnRealtimeConnectRequest().Bind(&hook.Handler[*core.RealtimeConnectRequestEvent]{
Func: func(e *core.RealtimeConnectRequestEvent) error {
t.registerEventCall("OnRealtimeConnectRequest")
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark" />
<title>Base Admin</title>
<script type="module" crossorigin src="/assets/index-D7VD1BqT.js"></script>
<script type="module" crossorigin src="/assets/index-CwoAdfZi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Clip6ok4.css">
</head>
<body>
-1
View File
@@ -6,7 +6,6 @@ const navItems = [
{ to: '/settings/smtp', label: 'SMTP' },
{ to: '/settings/backups', label: 'Backups' },
{ to: '/settings/auth', label: 'Auth providers' },
{ to: '/settings/mail', label: 'Mail templates' },
{ to: '/settings/tokens', label: 'Token options' },
{ to: '/settings/data', label: 'Import / Export' },
{ to: '/settings/superusers', label: 'Superusers' },