UserPermission 0.1.0

dotnet add package UserPermission --version 0.1.0
                    
NuGet\Install-Package UserPermission -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="UserPermission" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="UserPermission" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="UserPermission" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add UserPermission --version 0.1.0
                    
#r "nuget: UserPermission, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package UserPermission@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=UserPermission&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=UserPermission&version=0.1.0
                    
Install as a Cake Tool

user-permission-cs

NuGet License NuGet Version NuGet Downloads

ユーザーとグループを管理するための非同期ライブラリ user-permission.NET (C#) バインディング です。

Rust 実装本体は別リポジトリにあります: mokuichi147/user-permission

このリポジトリは Rust の C ABI (cdylib) を P/Invoke で呼び出す薄いラッパーのみを含み、async/await でそのまま使える API を提供します。2 つの NuGet パッケージを公開します。

  • UserPermission — ライブラリ本体
  • UserPermission.Toolserve コマンドを持つ .NET ツール (dnx / dotnet tool)

クイックスタート (サーバーを試す)

.NET 10 SDK があれば、インストール不要で dnx(ワンショット実行)から同梱サーバーを直接起動できます。

dnx UserPermission.Tool serve --webui

従来の SDK では、グローバルツールとしてインストールして使えます。

dotnet tool install -g UserPermission.Tool
user-permission serve --webui

ブラウザで http://127.0.0.1:8000/ui を開くと Web 管理画面が利用できます。 オプション一覧は サーバー起動 を参照してください。

インストール

dotnet add package UserPermission

ネイティブライブラリ (user-permission-core の Rust コア) は各プラットフォーム向けに同梱されています。対応 RID:

OS アーキテクチャ RID
Linux x64 / arm64 linux-x64 / linux-arm64
macOS Intel / Apple Silicon osx-x64 / osx-arm64
Windows x64 win-x64

対応ターゲットフレームワーク

netstandard2.0net8.0 のマルチターゲットです。

  • .NET 8+ / .NET Core 3.1+ / .NET 5・6・7: ネイティブライブラリは runtimes/{rid}/native/ から自動解決されます(追加設定不要)。
  • .NET Framework 4.6.1+ など (netstandard2.0 経由): 参照・コンパイルは可能ですが、runtimes/ による RID 別ネイティブの自動配置をランタイムがサポートしないため、対象プラットフォームのネイティブライブラリ(例: user_permission_csharp.dll)を実行ファイルと同じディレクトリに手動配置するか PATH を通す必要があります。

使い方

初期化

using UserPermission;

// 初回実行時にシークレットキーを自動生成(以降はファイルから読み込み)
await using var db = await Database.ConnectAsync("app.db", secret: "secret.key");

User user = await db.Users.CreateAsync("alice", "password123");
Group group = await db.Groups.CreateAsync("admins");

Database.ConnectAsync(...) は生成と接続をまとめて行います。生成と接続を分けたい場合は次のようにします。

var db = new Database("app.db", secret: "secret.key");
await db.ConnectAsync();
// ...
await db.DisposeAsync();   // または using/await using で自動解放

最初に作成したユーザーは自動的に管理者グループ (admin) に所属し、管理者になります。

ユーザー管理

User user = await db.Users.CreateAsync("alice", "password123", displayName: "Alice");
User? byId = await db.Users.GetByIdAsync(1);
User? byName = await db.Users.GetByUsernameAsync("alice");
IReadOnlyList<User> users = await db.Users.ListAllAsync();

await db.Users.UpdateAsync(user.Id, password: "new_password");
await db.Users.UpdateAsync(user.Id, displayName: "Alice Smith");
await db.Users.UpdateAsync(user.Id, isActive: false);
await db.Users.DeleteAsync(user.Id);

認証・トークン

string? token = await db.LoginAsync("alice", "password123");
string? token2 = await db.LoginAsync("alice", "password123", expires: TimeSpan.FromHours(24));

// トークンを検証してユーザーを解決(無効・期限切れは null)
User? resolved = await db.VerifyTokenAndGetUserAsync(token);
Console.WriteLine(resolved!.Id);                       // ユーザーID
Console.WriteLine(resolved.Username);                  // ユーザー名
Console.WriteLine(await db.Users.IsAdminAsync(resolved.Id));  // bool

グループ管理

Group group = await db.Groups.CreateAsync("editors", description: "Editor group");
Group? byId = await db.Groups.GetByIdAsync(1);
Group? byName = await db.Groups.GetByNameAsync("editors");
IReadOnlyList<Group> groups = await db.Groups.ListAllAsync();
IReadOnlyList<Group> adminGroups = await db.Groups.ListAdminGroupsAsync();

await db.Groups.UpdateAsync(group.Id, description: "Updated description");
await db.Groups.DeleteAsync(group.Id);

グループメンバー管理

await db.Groups.AddUserAsync(group.Id, user.Id);
await db.Groups.RemoveUserAsync(group.Id, user.Id);
IReadOnlyList<User> members = await db.Groups.GetMembersAsync(group.Id);
IReadOnlyList<Group> userGroups = await db.Groups.GetUserGroupsAsync(user.Id);

サーバー起動

同梱の axum HTTP サーバーを起動できます。

using UserPermission;

await Server.ServeAsync(host: "0.0.0.0", port: 8001, prefix: "/api", webui: true);

ServeAsync はサーバーが停止するまで完了しない Task を返します。

引数 デフォルト 説明
database user_permission.db SQLiteデータベースのパス
secret secret.key シークレットキーファイルのパス
host 127.0.0.1 バインドアドレス
port 8000 バインドポート
prefix (なし) APIルートプレフィックス(例: /api
webui false Web管理画面を有効化
webuiPrefix /ui 管理画面のURLプレフィックス

CLI からも起動できます(UserPermission.Tool パッケージ)。

# .NET 10: インストール不要のワンショット実行
dnx UserPermission.Tool serve --host 0.0.0.0 --port 8001 --prefix /api --webui

# もしくはグローバルツールとして
dotnet tool install -g UserPermission.Tool
user-permission serve --host 0.0.0.0 --port 8001 --prefix /api --webui
オプション デフォルト 説明
--host 127.0.0.1 バインドアドレス
--port 8000 バインドポート
--database user_permission.db SQLiteデータベースのパス
--secret secret.key シークレットキーファイルのパス
--prefix (なし) APIルートプレフィックス(例: /api
--webui 無効 Web管理画面を有効化
--webui-prefix /ui 管理画面のURLプレフィックス

ユーザー管理などフル機能の CLI サーバーは Rust リポジトリ側にあります (cargo install user-permission)。この .NET ツールは serve のみを提供します。

リレー(中継)

Database に URL を渡すと、ローカル SQLite と中央サーバーを同じインターフェースで切り替えられます。

using UserPermission;

// 初期化は local / relay 共通。接続文字列だけを変える。
await using var local = await Database.ConnectAsync("app.db", secret: "secret.key"); // ファイルパス → ローカル SQLite
await using var relay = await Database.ConnectAsync("http://localhost:8001");         // URL → HTTP 中継

// 以降の操作はどちらの backend でも同一
string? token = await relay.LoginAsync("alice", "password123");
IReadOnlyList<User> users = await relay.Users.ListAllAsync();

db.LoginAsync(...) で取得したトークンは Database が内部に保持し、以降のリクエストの Authorization: Bearer に自動付与されます。

推奨: backend を意識しない実装。 認証 (LoginAsync / LoginServiceAsync)、トークン検証 (VerifyTokenAndGetUserAsync)、ユーザー・グループ操作はすべてローカル / リレーで同一の呼び出しで動作します。接続先 URL(またはファイルパス)を切り替えるだけで、アプリ側のコードを変えずにローカル運用と中央サーバー運用を行き来できます。

// どちらの backend でも同じコードが動く
async Task<User?> AuthenticateAsync(Database db, string username, string password)
{
    // login 失敗時は null、VerifyTokenAndGetUserAsync は null を渡すと null を返す
    string? token = await db.LoginAsync(username, password);
    return await db.VerifyTokenAndGetUserAsync(token);
}

サービスクライアントの管理操作だけは例外でローカル専用です(後述)。

サービスクライアント認証(client-credentials)

サービス間連携向けに、平文パスワードを持たずに読み取り専用のスコープ付きトークンを発行できます。 クライアントには users:read / groups:read のスコープのみ付与でき、書き込みや管理操作はできません。

using UserPermission;

// 管理側(ローカル)でサービスクライアントを発行。secret は発行時のみ取得可能。
await using (var admin = await Database.ConnectAsync("app.db", secret: "secret.key"))
{
    var (client, secret) = await admin.ServiceClients.CreateAsync("reader", new[] { Scopes.UsersRead });
    Console.WriteLine($"{client.ClientId} / {secret}");
}

// リレー側はサービストークンでログインし、スコープ内のみ読み取れる。
await using (var relay = await Database.ConnectAsync("http://localhost:8001"))
{
    await relay.LoginServiceAsync(clientId, secret);
    IReadOnlyList<User> users = await relay.Users.ListAllAsync();  // users:read があるので OK
}

注意: 管理操作はローカル backend 専用です。 ServiceClients.CreateAsync / ListAsync / GetByClientIdAsync / DeleteAsync / RotateSecretAsync はリレー(URL)backend で呼ぶと例外になります(サービスクライアントの発行・管理は中央サーバー側で行う設計のため)。一方、サービス認証 (db.LoginServiceAsync(...)) はローカル / リレーのどちらでも動作します。

スコープの検証は Scopes.Validate(...) で行えます(未知のスコープがあれば UserPermissionException を送出)。

Scopes.Validate(new[] { Scopes.UsersRead, Scopes.GroupsRead });

バックエンド非依存のユーティリティ

ローカル / リレーのどちらでも同じ呼び出しで動作します。

// トークンを検証してユーザーを解決(無効・期限切れ・サービストークンは null)
User? user = await db.VerifyTokenAndGetUserAsync(token);

// 管理者が不在なら作成して昇格(リレーでは no-op で null)
await db.BootstrapAdminIfNeededAsync("admin", "password123");

エラーハンドリング

操作が失敗すると UserPermissionException が送出されます。Kind プロパティで種別を判別できます。

try
{
    await db.Users.CreateAsync("alice", "password123");
}
catch (UserPermissionException ex) when (ex.Kind == UserPermissionErrorKind.Conflict)
{
    // ユーザー名が既に存在する
}

REST API・管理者ロール・スキーマ

REST エンドポイント仕様、groups.is_admin による管理者ロールの扱い、データベーススキーマは Rust リポジトリの README を参照してください。

アーキテクチャ

┌─────────────────────────┐
│ C# (UserPermission.dll) │  Database / UserManager / GroupManager / ...
│  P/Invoke ([DllImport])  │  ← async Task<T>(内部で Task.Run)
└────────────┬────────────┘
             │ C ABI(UTF-8文字列 + JSON エンベロープ)
┌────────────▼────────────────────────┐
│ Rust cdylib                          │  user_permission_csharp
│  (crates/user-permission-csharp)     │  ← tokio ランタイムで block_on
└────────────┬─────────────────────────┘
             │
┌────────────▼─────────────────────────┐
│ user-permission-core / user-permission│  crates.io(SQLite/Argon2/JWT/axum)
└──────────────────────────────────────┘
  • Rust 側は各関数が JSON エンベロープ ({"ok": …} / {"err": {"kind","message"}}) を返し、C# 側でデシリアライズ・例外変換します。
  • 非同期処理は Rust 側の共有 tokio ランタイムで block_on し、C# 側は Task.Run でラップして async API にしています。

開発

前提: .NET SDK 8.0+Rust toolchain

# 1. ネイティブライブラリ (cdylib) をビルド
cargo build --release            # もしくは開発中は cargo build(debug)

# 2. C# ライブラリのビルドとテスト
#    Directory.Build.targets が target/{profile}/ のネイティブ lib を
#    各プロジェクトの出力へ自動コピーするため、別途配置は不要。
dotnet test

リリース

v で始まる Git タグを push すると GitHub Actions が全プラットフォームのネイティブライブラリをビルドし、 NuGet パッケージ (UserPermissionUserPermission.Tool) を生成して nuget.org に公開します(詳細は .github/workflows/release.yml)。

ライセンス

MIT OR Apache-2.0

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 90 6/2/2026