- golem15.users.manage_backend_links (no default role), en and pl labels - record actions on summercms.cabana.users: link_frontend_account asks for the frontend user's email (exact, case-insensitive, active users only), records linked_by and fires the link events; unlink_frontend_account (danger) removes the link - a taken or unknown account answers 422 on the prompt
129 lines
4.8 KiB
Go
129 lines
4.8 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/sm-user-plugin/classes"
|
|
"git.golem15.com/golem15/sm-user-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var _ pact.HasAdminActionExtensions = (*Plugin)(nil)
|
|
|
|
// AdminActionExtensions adds two record actions to the admin's
|
|
// Administrators screen (D-07, D-34), both behind
|
|
// golem15.users.manage_backend_links: link_frontend_account asks for the
|
|
// email of an active frontend user and links that user to the
|
|
// administrator shown (exact, case-insensitive match; no search and no
|
|
// list), recording who set it; unlink_frontend_account removes the
|
|
// administrator's link. cabana's outranking check runs before either.
|
|
func (p *Plugin) AdminActionExtensions() []pact.AdminActionExtension {
|
|
return []pact.AdminActionExtension{{
|
|
Controller: cabana.AdministratorsControllerID,
|
|
RecordActions: []pact.AdminRecordAction{
|
|
{
|
|
Name: "link_frontend_account",
|
|
Label: backendLinkLang + "set",
|
|
Confirm: backendLinkLang + "set_confirm",
|
|
Permissions: []string{classes.PermissionManageBackendLinks},
|
|
Prompt: &pact.AdminActionPrompt{
|
|
Label: backendLinkLang + "prompt_email",
|
|
Comment: backendLinkLang + "prompt_comment",
|
|
MaxLength: 254,
|
|
},
|
|
Applies: func(ctx context.Context, record any) (bool, error) {
|
|
link, err := p.adminRecordLink(ctx, record)
|
|
return link == nil, err
|
|
},
|
|
Run: p.setFrontendLink,
|
|
},
|
|
{
|
|
Name: "unlink_frontend_account",
|
|
Label: backendLinkLang + "remove",
|
|
Confirm: backendLinkLang + "remove_confirm",
|
|
Danger: true,
|
|
Permissions: []string{classes.PermissionManageBackendLinks},
|
|
Applies: func(ctx context.Context, record any) (bool, error) {
|
|
link, err := p.adminRecordLink(ctx, record)
|
|
return link != nil, err
|
|
},
|
|
Run: p.removeFrontendLink,
|
|
},
|
|
},
|
|
}}
|
|
}
|
|
|
|
// adminRecordLink returns the link of the administrator record, or nil.
|
|
func (p *Plugin) adminRecordLink(ctx context.Context, record any) (*models.BackendLink, error) {
|
|
admin, ok := record.(*cabana.BackendUser)
|
|
if !ok || admin == nil {
|
|
return nil, errors.New("golem15.user: the record is not an administrator")
|
|
}
|
|
return p.backendLinks().LinkFor(ctx, admin.ID)
|
|
}
|
|
|
|
// payloadError is the 422 on the prompt's input.
|
|
func payloadError(key string) error {
|
|
return &cabana.ValidationError{Details: map[string]any{"payload": []string{backendLinkLang + key}}}
|
|
}
|
|
|
|
// setFrontendLink links the active frontend user whose email is the prompt
|
|
// value to the administrator record.
|
|
func (p *Plugin) setFrontendLink(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
|
admin, ok := in.Record.(*cabana.BackendUser)
|
|
if !ok || admin == nil {
|
|
return pact.AdminRecordActionResult{}, errors.New("golem15.user: the record is not an administrator")
|
|
}
|
|
tx, ok := cabana.TxFromContext(ctx)
|
|
if !ok {
|
|
return pact.AdminRecordActionResult{}, errors.New("golem15.user: linking needs the action's transaction")
|
|
}
|
|
email := strings.TrimSpace(in.Value)
|
|
var ids []uint
|
|
if err := tx.Session(&gorm.Session{NewDB: true}).WithContext(ctx).Model(&models.User{}).
|
|
Where("lower(email) = lower(?) AND is_activated = TRUE", email).Limit(2).Pluck("id", &ids).Error; err != nil {
|
|
return pact.AdminRecordActionResult{}, err
|
|
}
|
|
if email == "" || len(ids) != 1 {
|
|
return pact.AdminRecordActionResult{}, payloadError("unknown_user")
|
|
}
|
|
var by uint
|
|
if caller, ok := bouncer.User(ctx); ok && caller != nil {
|
|
by = caller.ID
|
|
}
|
|
_, err := p.backendLinks().SetLink(ctx, tx, ids[0], admin.ID, by)
|
|
switch {
|
|
case err == nil:
|
|
case errors.Is(err, classes.ErrAlreadyLinked):
|
|
return pact.AdminRecordActionResult{}, payloadError("already_linked")
|
|
case errors.Is(err, classes.ErrFrontendUserUnavailable):
|
|
return pact.AdminRecordActionResult{}, payloadError("unknown_user")
|
|
case errors.Is(err, classes.ErrBackendUserUnavailable):
|
|
return pact.AdminRecordActionResult{}, &cabana.ForbiddenError{}
|
|
default:
|
|
return pact.AdminRecordActionResult{}, err
|
|
}
|
|
return pact.AdminRecordActionResult{Message: backendLinkLang + "set_success"}, nil
|
|
}
|
|
|
|
// removeFrontendLink removes the administrator record's link.
|
|
func (p *Plugin) removeFrontendLink(ctx context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
|
tx, ok := cabana.TxFromContext(ctx)
|
|
if !ok {
|
|
return pact.AdminRecordActionResult{}, errors.New("golem15.user: unlinking needs the action's transaction")
|
|
}
|
|
link, err := p.adminRecordLink(ctx, in.Record)
|
|
if err != nil {
|
|
return pact.AdminRecordActionResult{}, err
|
|
}
|
|
if err := p.backendLinks().RemoveLink(ctx, tx, link); err != nil {
|
|
return pact.AdminRecordActionResult{}, err
|
|
}
|
|
return pact.AdminRecordActionResult{Message: backendLinkLang + "remove_success"}, nil
|
|
}
|