Support atomic organization role sets and owner-managed invitations
verify / verify (push) Successful in 4m17s
verify / verify (push) Successful in 4m17s
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -8,6 +8,19 @@ application concern belongs in the shared module.
|
||||
|
||||
## Gamertan accounts and commerce
|
||||
|
||||
- A customer may need both purchasing and billing access. Replacing one role at
|
||||
a time would create partial permission states and misleading audit history.
|
||||
The role-set extension commits all direct roles together with optimistic
|
||||
binding IDs and current owner authority. Multiple-role invitations carry the
|
||||
same combination atomically, with stored owner-managed policy rechecked when
|
||||
accepted. SQLite tests cover concurrent winners, write rollback, demoted or
|
||||
removed grantors, and attempted implicit reactivation of suspended members.
|
||||
This adds schema 10; application vocabulary, allowed roles, invitation delivery,
|
||||
ordinary-customer authentication, and UI/API commands remain application-owned.
|
||||
- The public export allowlist omitted the owned-organization files introduced
|
||||
in preview 22. Including them and building the exported tree tests the actual
|
||||
distribution boundary rather than only comparing its path list with itself.
|
||||
|
||||
- Shared business purchasing exposed the difference between an initial member
|
||||
and an initial RBAC owner. The historical organization creation method commits
|
||||
membership but no access binding. The new `CreateOwnedOrganization` extension
|
||||
|
||||
@@ -26,7 +26,7 @@ The packages are ordinary Go imports. Pin the current preview and verify its
|
||||
module checksum:
|
||||
|
||||
```bash
|
||||
go get gamertan.com/web/requestmeta@v0.1.0-preview.22
|
||||
go get gamertan.com/web/requestmeta@v0.1.0-preview.23
|
||||
go mod verify
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import "gamertan.com/web/requestmeta"
|
||||
and request the containing module at an exact version:
|
||||
|
||||
```bash
|
||||
go get gamertan.com/web/requestmeta@v0.1.0-preview.22
|
||||
go get gamertan.com/web/requestmeta@v0.1.0-preview.23
|
||||
```
|
||||
|
||||
Only imported packages are compiled and linked. The packages nevertheless
|
||||
|
||||
+55
-7
@@ -8,17 +8,33 @@ environments; environments own application services. Teams are optional groups
|
||||
of active organization members.
|
||||
|
||||
`organizations.Service` creates those resources and issues digest-backed,
|
||||
expiring, single-use invitations. An invitation may carry one direct role and
|
||||
up to sixteen reviewed team memberships. Acceptance verifies that the
|
||||
expiring, single-use invitations. An invitation may carry up to sixteen direct
|
||||
roles and sixteen reviewed team memberships. Acceptance verifies that the
|
||||
authenticated user's normalized email matches and applies the membership,
|
||||
role, teams, consumption marker, and audit event in one transaction.
|
||||
When `OwnerRole` is configured, creating or revoking an invitation carrying
|
||||
that role additionally requires a current active direct owner inside the same
|
||||
SQLite transaction. A broad access-management permission may administer
|
||||
ordinary invitations but cannot create or cancel owner access.
|
||||
roles, teams, consumption marker, and audit event in one transaction. The
|
||||
recipient and issuing member must remain active, fully registered users of an
|
||||
active organization; a suspended recipient cannot use an invitation as implicit
|
||||
reactivation. Duplicate or concurrent acceptance consumes the token only once.
|
||||
When `OwnerRole` is configured, invitations granting that role require a current
|
||||
direct owner at creation and acceptance, and an owner for revocation. Set
|
||||
`OwnerManagedInvitations: true` to apply that rule to every invitation, including
|
||||
ordinary member invitations. Stored `RequiredOwnerRole` preserves the boundary
|
||||
even when a link reaches another application service with different options.
|
||||
A broad access-management permission can still administer ordinary invitations
|
||||
when owner-managed policy is disabled, but cannot create or cancel owner access.
|
||||
Applications own invitation pages, email or out-of-band delivery, active-source
|
||||
checks before archival, and account recovery.
|
||||
|
||||
Use `InviteWithAccess.DirectRoles` for combinations and `RequestID` for the
|
||||
creation audit correlation. `DirectRole` remains the legacy single-role form;
|
||||
supplying both is rejected, not merged. The service copies and sorts role arrays
|
||||
and rejects duplicates or unknown/unseeded roles before persistence. Repository
|
||||
adapters implement `RoleInvitationRepository` to store and enforce role-set and
|
||||
owner requirements atomically. An unsupported adapter returns
|
||||
`ErrRoleInvitationUnsupported`; it must not issue a partly effective invitation.
|
||||
The application restricts which roles may be offered and authenticates the actor;
|
||||
never accept the owner-role policy or actor identity from submitted fields.
|
||||
|
||||
## Creating an organization with an owner
|
||||
|
||||
For an existing authenticated user creating a business, use
|
||||
@@ -36,6 +52,7 @@ a customer organization grants no authority in any other organization.
|
||||
```go
|
||||
customers, err := organizations.New(store, organizations.Options{
|
||||
OwnerRole: "customer.owner", // Application-defined, already seeded.
|
||||
OwnerManagedInvitations: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -83,6 +100,37 @@ will not demote the final active direct owner. The application must still
|
||||
authorize the administrator and bind any required fresh passkey assertion to
|
||||
the organization, target user, target role, and expected IDs.
|
||||
|
||||
For combinations such as Buyer plus Billing Manager, use
|
||||
`access.ReplaceOrganizationUserRoles` with a non-empty, unique `Roles` array
|
||||
(maximum sixteen) and the same `ExpectedBindingIDs` convention. This operation
|
||||
requires a current direct owner inside the write transaction for every change;
|
||||
the older single-role API retains its delegated non-owner administration policy.
|
||||
The replacement is all-or-nothing, leaves narrower grants untouched, and records
|
||||
one audit. `ErrRoleChangeConflict` means refresh the displayed bindings, not retry
|
||||
the old request silently. `RoleSetRepository` is required; separate grant/revoke
|
||||
calls are not a fallback. A basic-member role with no permissions can represent
|
||||
membership without purchasing or billing access.
|
||||
|
||||
Role names and capabilities remain application policy. In particular, customer
|
||||
roles must not be replaceable with merchant roles merely because both policies
|
||||
use the same database. Routine customer changes do not inherently require a
|
||||
passkey ceremony; the application decides when an action needs fresh proof.
|
||||
|
||||
## Schema 10 compatibility
|
||||
|
||||
Schema 10 adds `direct_roles_json` and `required_owner_role` to stored invitations.
|
||||
The explicit migration preserves legacy `direct_role`, hashes, dates, teams, and
|
||||
consumption state. It does not guess which application role historically meant
|
||||
owner. Configure the correct `OwnerRole` when accepting pre-schema-10 owner
|
||||
invitations; that service policy supplies their acceptance-time owner check.
|
||||
New invitations carry the persisted requirement themselves.
|
||||
|
||||
Use `OpenWithOptions(..., OpenOptions{Migrate: false})` plus
|
||||
`RequireCurrentSchema` at application startup and an explicit operator migration
|
||||
command. Retain a verified backup before migrating. Schema-9 binaries reject
|
||||
schema 10 when using the startup check and are not approved writers after the
|
||||
upgrade; a binary rollback must not overwrite newer accepted data.
|
||||
|
||||
`access.Service` evaluates a permission against a complete resource scope:
|
||||
|
||||
```go
|
||||
|
||||
@@ -9,3 +9,8 @@ the exact same exported tree as a read-only discovery mirror.
|
||||
The exporter includes no branches, reflogs, private operational evidence,
|
||||
credentials, databases, logs, or development-only files. Public Gitea issues
|
||||
and pull requests are the contribution venue.
|
||||
|
||||
`scripts/test-public-snapshot.sh` checks the exact allowlist and builds all
|
||||
exported packages. New implementation and regression-test files must be included
|
||||
explicitly; a successful build in the development checkout does not prove that
|
||||
the smaller exported distribution is complete.
|
||||
|
||||
Reference in New Issue
Block a user