120 lines
3.6 KiB
Go
120 lines
3.6 KiB
Go
package delivery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// errUnknownTargetTypeForEdit is returned when a stored target has a
|
|
// type the edit form has no field set for.
|
|
var errUnknownTargetTypeForEdit = errors.New(
|
|
"unknown target type",
|
|
)
|
|
|
|
// TargetConfigForm is the UNMASKED projection of a target's stored
|
|
// configuration, for pre-filling the target edit form.
|
|
//
|
|
// It is the deliberate exception to the rule TargetView enforces
|
|
// everywhere else: TargetView exists so that no template can render
|
|
// a target's stored blob, because a destination URL's path segments
|
|
// and a header value are both routinely the credential. An operator
|
|
// cannot correct a value they cannot see, so the edit form — and
|
|
// only the edit form — is shown the full value.
|
|
//
|
|
// Everything that keeps that exception narrow lives at the call
|
|
// site: the route is behind RequireAuth and the webhook's ownership
|
|
// check, and its group sets NoCache so the rendered secret is not
|
|
// written to a shared cache. Do not reach for this type from any
|
|
// other page.
|
|
type TargetConfigForm struct {
|
|
// URL is the destination for an HTTP target and the webhook
|
|
// URL for a Slack target.
|
|
URL string
|
|
// Headers is the HTTP target's configured headers in the
|
|
// textarea representation, one "Name: value" per line.
|
|
Headers string
|
|
// Timeout is the HTTP target's per-request timeout in seconds,
|
|
// empty when unset.
|
|
Timeout string
|
|
// Expiry is the database (archive) target's row expiry.
|
|
Expiry string
|
|
}
|
|
|
|
// NewTargetConfigForm parses a target's stored configuration into
|
|
// the edit form's fields.
|
|
//
|
|
// A configuration that does not parse is an error rather than a
|
|
// zero-valued form that silently looks like a target with no
|
|
// settings. The caller shows the operator that the stored value
|
|
// could not be read, so that saving the form is understood as
|
|
// replacing it rather than preserving it.
|
|
func NewTargetConfigForm(
|
|
t *database.Target,
|
|
) (TargetConfigForm, error) {
|
|
switch t.Type {
|
|
case database.TargetTypeHTTP:
|
|
cfg, err := parseHTTPConfig(t.Config)
|
|
if err != nil {
|
|
return TargetConfigForm{}, err
|
|
}
|
|
|
|
return TargetConfigForm{
|
|
URL: cfg.URL,
|
|
Headers: FormatTargetHeaders(cfg.Headers),
|
|
Timeout: FormatTargetTimeout(cfg.Timeout),
|
|
}, nil
|
|
case database.TargetTypeSlack:
|
|
cfg, err := parseSlackConfig(t.Config)
|
|
if err != nil {
|
|
return TargetConfigForm{}, err
|
|
}
|
|
|
|
return TargetConfigForm{URL: cfg.WebhookURL}, nil
|
|
case database.TargetTypeDatabase:
|
|
return databaseConfigForm(t.Config)
|
|
case database.TargetTypeLog:
|
|
// The log target takes no configuration.
|
|
return TargetConfigForm{}, nil
|
|
default:
|
|
return TargetConfigForm{}, fmt.Errorf(
|
|
"%w: %q", errUnknownTargetTypeForEdit, t.Type,
|
|
)
|
|
}
|
|
}
|
|
|
|
// databaseConfigForm parses an archive target's optional expiry.
|
|
// An absent or empty configuration is the keep-forever default and
|
|
// yields an empty field, so re-saving the form unchanged stores the
|
|
// same empty configuration it started with. An expiry that is set
|
|
// but not a valid duration is an error, not a blank field.
|
|
func databaseConfigForm(
|
|
configJSON string,
|
|
) (TargetConfigForm, error) {
|
|
if configJSON == "" {
|
|
return TargetConfigForm{}, nil
|
|
}
|
|
|
|
var cfg databaseTargetConfig
|
|
|
|
err := json.Unmarshal([]byte(configJSON), &cfg)
|
|
if err != nil {
|
|
return TargetConfigForm{}, fmt.Errorf(
|
|
"parsing config JSON: %w", err,
|
|
)
|
|
}
|
|
|
|
if cfg.Expiry == "" || cfg.Expiry == archiveExpiryNever {
|
|
return TargetConfigForm{}, nil
|
|
}
|
|
|
|
err = ValidateArchiveExpiry(cfg.Expiry)
|
|
if err != nil {
|
|
return TargetConfigForm{}, err
|
|
}
|
|
|
|
return TargetConfigForm{Expiry: cfg.Expiry}, nil
|
|
}
|