Documentation
¶
Overview ¶
Package magiclink provides optional, existing-account email sign-in using public plugin, storage, mail and session contracts. PostgreSQL is required.
Index ¶
Examples ¶
Constants ¶
const ID = "magic-link"
const Lifetime = 10 * time.Minute
const RedeemOperation plugin.Operation = "magic_link.redeem"
RedeemOperation names the audit event recorded when a proof is rejected. Successful redemption is recorded by core as plugin.SessionIssue.
Variables ¶
This section is empty.
Functions ¶
func Contract ¶
func Contract() plugin.Definition
Contract returns the plugin's declarative API description for side-effect free export through sigil.Describe (for example by the sigil CLI). It is not a runnable registration: its handlers have no bound Auth and fail closed.
func PruneExpired ¶
func PruneExpired(ctx context.Context, store storage.Store, before time.Time, limit int) (int64, error)
PruneExpired deletes at most limit (1..1000) challenges whose expiry, clamped to database time, is at or before cutoff, and returns the count. Consumed rows leave with their expiry; no removed row could still redeem. It needs only the store, because the plugin's persisted ledger ownership survives disablement: operators keep the table bounded while the plugin is disabled or before re-enabling it. A store where the plugin was never installed is ErrUnsupported. User deletion cascades independently through the foreign key.
Types ¶
type Config ¶
type Config struct {
Store storage.Store
Delivery *sigil.MailDelivery
Render func(context.Context, string, sigil.Secret) (sigil.Mail, error)
}
Config resources must belong to the same deployment and Store as the Auth passed to Bind. Render must be local, cancellation-aware and concurrency-safe; it receives a secret to embed in a host-owned confirmation page's URL fragment.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Example ¶
This compiling host integration accepts provisioned host resources. Run the migration block in an explicit operator command before starting runtime hosts.
package main
import (
"context"
"database/sql"
"net/http"
sigil "github.com/brewgenixapp/sigil"
"github.com/brewgenixapp/sigil/plugin"
"github.com/brewgenixapp/sigil/plugin/magiclink"
"github.com/brewgenixapp/sigil/storage/postgres"
)
func main() {
build := func(ctx context.Context, db *sql.DB, secret sigil.Secret, protector sigil.Protector, sender sigil.MailSender) (http.Handler, *sigil.Auth, *sigil.MailDelivery, error) {
store, err := postgres.New(db, "public")
if err != nil {
return nil, nil, nil, err
}
delivery, err := sigil.NewMailDelivery(store, protector, sender)
if err != nil {
return nil, nil, nil, err
}
links, err := magiclink.New(magiclink.Config{Store: store, Delivery: delivery, Render: func(_ context.Context, _ string, token sigil.Secret) (sigil.Mail, error) {
// The host serves this page and requires an explicit Sign in button. No GET
// or page-load script may redeem the proof. Never log or track these URLs.
return sigil.Mail{Subject: sigil.NewSecret("Sign in to Example"), Text: sigil.NewSecret("Confirm your sign-in: https://app.example.com/magic-link#" + token.Reveal())}, nil
}})
if err != nil {
return nil, nil, nil, err
}
definitions := []plugin.Definition{links.Definition()}
registry, err := plugin.Compile(definitions, nil)
if err != nil {
return nil, nil, nil, err
}
core, err := postgres.NewMigrator(db, "public")
if err != nil {
return nil, nil, nil, err
}
if err := core.Apply(ctx); err != nil {
return nil, nil, nil, err
}
migrations, err := postgres.NewPluginMigrator(db, "public", registry)
if err != nil {
return nil, nil, nil, err
}
if err := migrations.Apply(ctx); err != nil {
return nil, nil, nil, err
}
// Runtime startup is read-only. Use a runtime database role here in production.
a, err := sigil.New(ctx, sigil.Config{BaseURL: "https://auth.example.com", TrustedOrigins: []string{"https://app.example.com"}, Secret: secret, Store: store, Plugins: definitions})
if err != nil {
return nil, nil, nil, err
}
if err := links.Bind(a); err != nil {
a.Close(ctx)
return nil, nil, nil, err
}
// Host schedules delivery.DispatchOne(ctx), owns the server and closes Auth.
return a.Handler(), a, delivery, nil
}
_ = build
}
Output:
func (*Plugin) Bind ¶
Bind attaches the startup-created Auth exactly once, before serving traffic. Register this instance's Definition and use the same Store in both configs.
func (*Plugin) Definition ¶
func (p *Plugin) Definition() plugin.Definition
func (*Plugin) Redeem ¶
func (p *Plugin) Redeem(ctx context.Context, ip netip.Addr, token sigil.Secret) (sigil.IssuedSession, error)
Redeem authenticates only after the hook consumes the proof in the SAME transaction as session issuance. Never retry an ambiguous commit automatically.