feat(v1.1.8): invitations flow (migration 0030 + accept_invite returns session)
migration 0030: app_user_invitations table — surrogate id PK + unique token_hash, app_id FK cascading, pre-stages email + display_name + roles for a user that doesn't exist yet. One-shot via atomic UPDATE SET accepted_at = NOW() WHERE accepted_at IS NULL. UsersServiceImpl gains invitations: Arc<dyn AppUserInvitationRepo> plus a mint_session() helper factored from login() and reused by accept_invite(). users::invite(email, opts) is gated on AppUsersAdmin (per brief — the most senior of the three new capabilities). Optional EmailTemplateOpts inside InviteOpts: omitting the template skips the email send so an admin can stamp invitations for out-of-band delivery (mailers, printed onboarding letters, etc.). If the template is present and the email service isn't configured, surfaces as NotConfigured; non-NotConfigured failures are logged but kept silent so the invitation row remains valid for retry. users::accept_invite(token, password, display_name?) atomically consumes the invitation, validates the new password, creates the user (returning () on DuplicateEmail — sign-up beat acceptance, they'll log in normally), and mints a fresh session via mint_session so the caller can return both the user and a working session token in one round trip. Pre-staged roles are stored on the invitation row but not yet applied — the app_user_roles table arrives in commit 8 (migration 0031). For commit 7 the staged-but-not-applied case logs an info record so an operator can audit the gap. list_invitations + revoke_invitation (admin-mediated, gated on AppUsersAdmin) ship in this commit and become reachable from the HTTP surface later in the series. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,10 @@ use picloud_manager_core::{
|
||||
FilesServiceImpl, FsFilesRepo, HttpConfig, HttpServiceImpl, KvServiceImpl, OutboxEventEmitter,
|
||||
OutboxRepo, PostgresAbandonedRepo, PostgresAdminSessionRepository, PostgresAdminUserRepository,
|
||||
PostgresApiKeyRepository, PostgresAppDomainRepository, PostgresAppMembersRepository,
|
||||
PostgresAppRepository, PostgresAppSecretsRepo, PostgresAppUserPasswordResetRepo,
|
||||
PostgresAppUserRepository, PostgresAppUserSessionRepository,
|
||||
PostgresAppUserVerificationRepo, PostgresDeadLetterRepo, PostgresDeadLetterService,
|
||||
PostgresAppRepository, PostgresAppSecretsRepo, PostgresAppUserInvitationRepo,
|
||||
PostgresAppUserPasswordResetRepo, PostgresAppUserRepository,
|
||||
PostgresAppUserSessionRepository, PostgresAppUserVerificationRepo, PostgresDeadLetterRepo,
|
||||
PostgresDeadLetterService,
|
||||
PostgresDocsRepo, PostgresExecutionLogRepository, PostgresExecutionLogSink, PostgresKvRepo,
|
||||
PostgresOutboxRepo, PostgresPubsubRepo, PostgresRouteRepository, PostgresScriptRepository,
|
||||
PostgresSecretsRepo, PostgresTopicRepo, PostgresTriggerRepo, PrincipalResolver,
|
||||
@@ -255,11 +256,14 @@ pub async fn build_app(
|
||||
Arc::new(PostgresAppUserVerificationRepo::new(pool.clone()));
|
||||
let app_user_password_resets_repo =
|
||||
Arc::new(PostgresAppUserPasswordResetRepo::new(pool.clone()));
|
||||
let app_user_invitations_repo =
|
||||
Arc::new(PostgresAppUserInvitationRepo::new(pool.clone()));
|
||||
let users: Arc<dyn UsersService> = Arc::new(UsersServiceImpl::new(
|
||||
app_users_repo.clone(),
|
||||
app_user_sessions_repo.clone(),
|
||||
app_user_verifications_repo.clone(),
|
||||
app_user_password_resets_repo.clone(),
|
||||
app_user_invitations_repo.clone(),
|
||||
email.clone(),
|
||||
authz.clone(),
|
||||
events.clone(),
|
||||
|
||||
Reference in New Issue
Block a user