feat(v1.1.8): per-app roles + roles in user record (migration 0031)

migration 0031: app_user_roles table — composite PK (app_id, user_id,
role) so add is idempotent (ON CONFLICT DO NOTHING). v1.1.8 stores
strings only; permission matrices / hierarchies / role registry are
explicitly v1.2 work per the brief.

UsersServiceImpl wires roles: Arc<dyn AppUserRoleRepo>:

  * fetch_roles() now actually queries the repo (replacing the empty
    Vec stub from commit 4). Every AppUser returned from get /
    find_by_email / list / update / verify / login now carries its
    role list.
  * users::add_role gated on AppUsersAdmin; first checks the user
    exists in this app so a FK violation can't leak "no such user".
  * users::remove_role gated on AppUsersAdmin; idempotent.
  * users::has_role gated on AppUsersRead.
  * accept_invite now applies pre-staged roles atomically with the
    user creation; malformed role strings are skipped with a warn
    rather than aborting the whole accept (the invitation was an
    admin's promise — we honor as much of it as we can).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 12:13:35 +02:00
parent b07382e64b
commit 3af99873c3
5 changed files with 240 additions and 30 deletions

View File

@@ -20,6 +20,7 @@ pub mod app_secrets_repo;
pub mod app_user_invitation_repo;
pub mod app_user_password_reset_repo;
pub mod app_user_repo;
pub mod app_user_role_repo;
pub mod app_user_session_repo;
pub mod app_user_verification_repo;
pub mod users_service;
@@ -97,6 +98,7 @@ pub use app_user_invitation_repo::{
pub use app_user_password_reset_repo::{
AppUserPasswordResetRepo, AppUserPasswordResetRepoError, PostgresAppUserPasswordResetRepo,
};
pub use app_user_role_repo::{AppUserRoleRepo, AppUserRoleRepoError, PostgresAppUserRoleRepo};
pub use app_user_verification_repo::{
AppUserVerificationRepo, AppUserVerificationRepoError, PostgresAppUserVerificationRepo,
};