Table of Contents

Interface IUserManagementService

Namespace
Tharga.Team
Assembly
Tharga.Team.dll

User administration operations: directory verification and user deletion. Authorization is enforced in the service layer by an authorization decorator; the [RequireScope] attributes here document the scope each operation requires. All operations require the Manage system scope. Directory-backed operations require a registered IUserDirectoryService.

public interface IUserManagementService

Methods

DeleteUserAsync(string, bool, CancellationToken)

Delete a user: removes the user from every team and deletes the user record (audited). With deleteFromDirectory the user is also deleted from the external directory; a directory failure does not roll back the local delete — it is reported on the result.

[RequireScope("users:manage")]
Task<UserDeleteResult> DeleteUserAsync(string userKey, bool deleteFromDirectory = false, CancellationToken cancellationToken = default)

Parameters

userKey string
deleteFromDirectory bool
cancellationToken CancellationToken

Returns

Task<UserDeleteResult>

GetDirectoryOnlyUsersAsync(CancellationToken)

List directory users that have no matching local user (matched by directory id, falling back to email), streamed as directory pages arrive.

[RequireScope("users:manage")]
IAsyncEnumerable<DirectoryUser> GetDirectoryOnlyUsersAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

Returns

IAsyncEnumerable<DirectoryUser>

GetOwnedTeamsAsync(string, CancellationToken)

The teams this user owns — the teams that deleting them would leave with no owner.

[RequireScope("users:manage")]
Task<IReadOnlyList<ITeam>> GetOwnedTeamsAsync(string userKey, CancellationToken cancellationToken = default)

Parameters

userKey string
cancellationToken CancellationToken

Returns

Task<IReadOnlyList<ITeam>>

Remarks

Meant to be asked before confirming a delete, so the operator can transfer ownership instead of learning afterwards that a team is unrecoverable: TransferOwnershipAsync requires the caller to be the owner, so once the owner is gone only a holder of SetOwner can repair it.

On IUserManagementService rather than ITeamService deliberately. The question is "what will deleting this user break", which is user administration; and it keeps the delete dialog off the internal team contract, which no component should inject.

SetUserDisabledAsync(string, bool, CancellationToken)

Disables the user, or enables them again. A disabled user keeps their record, their memberships and their history — the reversible alternative to DeleteUserAsync(string, bool, CancellationToken).

[RequireScope("users:manage")]
Task SetUserDisabledAsync(string userKey, bool disabled, CancellationToken cancellationToken = default)

Parameters

userKey string
disabled bool
cancellationToken CancellationToken

Returns

Task

Remarks

A caller cannot disable themselves. Enforced here rather than only hidden in the UI: an administrator who locks themselves out needs a second administrator to undo it, and refusing the self-case also guarantees somebody is left holding Manage.

This is not Disabled, which means disabled in the external directory. This one blocks the user from this application only, and the two are shown separately.

It does not cascade to the user's API keys. A key is not a session — it is an independent credential with its own lifecycle, and retiring a person's integrations is a separate deliberate act (which is also what keeps each one reversible on its own).

A signed-in user is not evicted instantly; they are signed out within ClaimRevalidationOptions.Interval.

Exceptions

InvalidOperationException

The caller is the user being disabled.

SetUserNameAsync(string, string, CancellationToken)

Set a user's display name, and — when o.Blazor.WriteNameToDirectory is enabled and the user is linked to a directory account — write it back to the external directory too.

[RequireScope("users:manage")]
Task<UserNameChangeResult> SetUserNameAsync(string userKey, string name, CancellationToken cancellationToken = default)

Parameters

userKey string
name string
cancellationToken CancellationToken

Returns

Task<UserNameChangeResult>

Remarks

The local write always happens; a failure there throws. The directory write is best-effort and its outcome is reported on the result rather than rolled back into the local one: they fail independently, and coupling them would let a directory outage block renaming a user here.

Administrative rename only. The self-service path (IUserService.SetUserNameAsync) stays local deliberately — a user editing their own display name in this application should not silently rewrite the organization's directory.

VerifyAllAsync(CancellationToken)

Verify all local users against the external directory, streamed as results arrive.

[RequireScope("users:manage")]
IAsyncEnumerable<UserVerificationResult> VerifyAllAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

Returns

IAsyncEnumerable<UserVerificationResult>

VerifyUserAsync(string, CancellationToken)

Verify a local user against the external directory. When the user resolves via email fallback, the found directory id is persisted on the user (relink).

[RequireScope("users:manage")]
Task<DirectoryVerificationResult> VerifyUserAsync(string userKey, CancellationToken cancellationToken = default)

Parameters

userKey string
cancellationToken CancellationToken

Returns

Task<DirectoryVerificationResult>