Skip to content

feat(auth,platform-objects): org join link V1 — sys_join_link + create/rotate/revoke/info/join, membership via governed addMember #11587

Description

@os-zhuang

Part of #11586 (epic — ruling and V1 scope live there; read it first).

Deliverable

The backend half of the universal org join link, V1:

Data — new platform object (suggested name sys_join_link; naming per sibling conventions in packages/platform-objects/src/identity/): organization_id (lookup sys_organization), token (opaque, ≥128-bit random, URL-safe), role semantics PINNED to member (not authorable in V1 — a constant, not a field, unless the schema layer makes a fixed field cheaper to audit), expires_at (default: creation +7d), revoked_at (nullable), max_uses (nullable), use_count, created_by. Exactly ONE active (non-revoked, non-expired) link per org — enforce at the write endpoint AND with a partial unique index if the driver supports it. Generic CRUD suppressed (managed via endpoints only, like sys_invitation).

Endpoints (mounted beside the better-auth organization surface; exact mount point is implementation detail):

Contract bookkeeping — Clause-② YES (this widens the public API surface): book any new feature-gated inputs in PUBLIC_AUTH_FEATURES (packages/spec/src/kernel/public-auth-features.ts, organization entry) per the bidirectional feature-gate guard; new error codes go wherever the error-code ledger requires. Expect the needs:contract-review chain regardless of dispatch tier (subagent tier is not self-provable).

Tests — authz matrix (stranger / plain member / admin / owner) on all mutating endpoints; expiry / revocation / rotation / exhaustion; verified-email gate on join; an assertion that join goes through the addMember path (not a raw insert); idempotent re-join; token-info leak surface (no org internals for invalid tokens).

Token storage note (PM suggestion, dev judges): retrievable-by-admin plaintext in the org-scoped row is acceptable for V1 (member-grade, expiring, revocable — unlike an OAuth client_secret); if hashed-at-rest with show-once is trivially compatible with the console's "copy link anytime" UX, flag the option in the report rather than deciding silently.

Non-goals (V1, ruled on the epic)

Domain allowlists, approval queue, per-link roles, multiple concurrent links, SCIM interplay.

Consumers to unblock

objectui (console dialog + /join/:token route) and cloud (control-plane org-scoping + E2E) sub-issues carry Blocked-by: pointing here — their cards state what they need: stable endpoint paths + error codes in this card's PR body.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions