71 lines
2.5 KiB
Go
71 lines
2.5 KiB
Go
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package organizations
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"gamertan.com/web/access"
|
|
)
|
|
|
|
// OwnedOrganization is one atomic creation command. Implementations must commit
|
|
// the organization, membership, direct owner binding, and both audits together.
|
|
type OwnedOrganization struct {
|
|
Organization Organization
|
|
Membership Membership
|
|
OwnerBinding access.Binding
|
|
OrganizationAudit AuditEvent
|
|
AccessAudit access.AuditEvent
|
|
}
|
|
|
|
// OwnedOrganizationRepository extends Repository without changing the legacy
|
|
// membership-only CreateOrganization contract. There is no non-atomic fallback.
|
|
type OwnedOrganizationRepository interface {
|
|
CreateOwnedOrganization(context.Context, OwnedOrganization) error
|
|
}
|
|
|
|
// CreateOwnedOrganization grants the configured OwnerRole to the initial owner
|
|
// inside the creation transaction. Applications authorize creation and choose
|
|
// OwnerRole when constructing the service, never from a submitted role name.
|
|
// The role must already be seeded in the repository.
|
|
func (service *Service) CreateOwnedOrganization(ctx context.Context, input CreateOrganization) (Organization, error) {
|
|
if service.ownerRole == "" {
|
|
return Organization{}, errors.New("organizations: owned creation requires a configured owner role")
|
|
}
|
|
repository, ok := service.repository.(OwnedOrganizationRepository)
|
|
if !ok {
|
|
return Organization{}, ErrOwnedCreationUnsupported
|
|
}
|
|
organization, membership, audit, err := service.prepareOrganization(input)
|
|
if err != nil {
|
|
return Organization{}, err
|
|
}
|
|
bindingID, err := token(service.random, 18)
|
|
if err != nil {
|
|
return Organization{}, err
|
|
}
|
|
accessAuditID, err := token(service.random, 18)
|
|
if err != nil {
|
|
return Organization{}, err
|
|
}
|
|
binding := access.Binding{
|
|
ID: bindingID, SubjectKind: access.User, SubjectID: input.OwnerUserID,
|
|
Role: service.ownerRole, Scope: access.Scope{OrganizationID: organization.ID},
|
|
GrantedBy: input.OwnerUserID, GrantedAt: organization.CreatedAt,
|
|
}
|
|
accessAudit := access.AuditEvent{
|
|
ID: accessAuditID, OrganizationID: organization.ID, ActorUserID: input.OwnerUserID,
|
|
Action: "access.binding.grant", ResourceType: "binding", ResourceID: bindingID,
|
|
RequestID: input.RequestID, Summary: "Initial organization owner granted",
|
|
CreatedAt: organization.CreatedAt,
|
|
}
|
|
if err = repository.CreateOwnedOrganization(ctx, OwnedOrganization{
|
|
Organization: organization, Membership: membership, OwnerBinding: binding,
|
|
OrganizationAudit: audit, AccessAudit: accessAudit,
|
|
}); err != nil {
|
|
return Organization{}, err
|
|
}
|
|
return organization, nil
|
|
}
|