リソースレベル権限システム 使用例¶
このドキュメントでは、リソースレベル権限システムの具体的な使用例を紹介します。
目次¶
基本的な権限の付与¶
例1: ユーザーにプロジェクトの閲覧権限を付与¶
curl -X POST http://localhost:3000/api/resource-permissions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"userId": "user-uuid-123",
"resourceType": "PROJECT",
"resourceId": "chibafes2024",
"permissions": ["READ"]
}'
例2: ユーザーに企画の編集権限を付与(期限付き)¶
curl -X POST http://localhost:3000/api/resource-permissions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"userId": "user-uuid-456",
"resourceType": "CIRCLE_PROJECT",
"resourceId": "circle-project-uuid-789",
"permissions": ["READ", "WRITE", "CHECKIN"],
"expiresAt": "2025-12-31T23:59:59Z"
}'
例3: 複数の権限を一度に付与¶
curl -X POST http://localhost:3000/api/resource-permissions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"userId": "user-uuid-789",
"resourceType": "PROJECT",
"resourceId": "chibafes2024",
"permissions": ["READ", "WRITE", "APPROVE", "ALLOCATE_RESOURCES"]
}'
ロールテンプレートの使用¶
例4: ロールテンプレート一覧の取得¶
# 全てのロールテンプレートを取得
curl -X GET http://localhost:3000/api/resource-role-templates \
-H "Authorization: Bearer YOUR_TOKEN"
# PROJECT用のロールテンプレートのみ取得
curl -X GET "http://localhost:3000/api/resource-role-templates?resourceType=PROJECT" \
-H "Authorization: Bearer YOUR_TOKEN"
レスポンス例:
{
"templates": [
{
"id": "template-uuid-1",
"name": "ProjectManager",
"resourceType": "PROJECT",
"permissions": ["READ", "WRITE", "APPROVE", "ALLOCATE_RESOURCES", "VIEW_PRIVATE"],
"description": "プロジェクト管理者用ロール",
"isSystem": true
},
{
"id": "template-uuid-2",
"name": "ProjectEditor",
"resourceType": "PROJECT",
"permissions": ["READ", "WRITE", "VIEW_PRIVATE"],
"description": "プロジェクト編集者用ロール",
"isSystem": true
}
]
}
例5: ロールテンプレートを使用して権限を付与¶
curl -X POST http://localhost:3000/api/resource-permissions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"userId": "user-uuid-123",
"resourceType": "PROJECT",
"resourceId": "chibafes2024",
"roleTemplate": "ProjectManager"
}'
例6: カスタムロールテンプレートの作成¶
curl -X POST http://localhost:3000/api/resource-role-templates \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "EventCoordinator",
"resourceType": "PROJECT",
"permissions": ["READ", "WRITE", "VIEW_PRIVATE"],
"description": "イベントコーディネーター用のカスタムロール"
}'
エンドポイントでの権限チェック¶
例7: 新しいエンドポイントでリソース権限を使用¶
// endpoints/my-protected-endpoint.ts
import { Hono } from 'hono';
import { requireResourcePermission } from '../lib/api/auth';
const app = new Hono();
// 企画の更新エンドポイント(WRITE権限が必要)
app.put('/circle-project/:id',
requireResourcePermission('CIRCLE_PROJECT', 'id', ['WRITE']),
async (c) => {
const id = c.req.param('id');
const body = await c.req.json();
// 権限チェック済みなので直接更新可能
const updated = await prisma.circleProject.update({
where: { id },
data: body,
});
return c.json({ success: true, data: updated });
}
);
// メンバー管理エンドポイント(MANAGE_MEMBERS権限が必要)
app.post('/circle-project/:id/members',
requireResourcePermission('CIRCLE_PROJECT', 'id', ['MANAGE_MEMBERS']),
async (c) => {
const id = c.req.param('id');
const { userId } = await c.req.json();
await prisma.projectMember.create({
data: { circleProjectId: id, userId },
});
return c.json({ success: true });
}
);
// 承認エンドポイント(APPROVE権限が必要)
app.post('/circle-project/:id/approve',
requireResourcePermission('CIRCLE_PROJECT', 'id', ['APPROVE']),
async (c) => {
const id = c.req.param('id');
const updated = await prisma.circleProject.update({
where: { id },
data: { status: 'APPROVED' },
});
return c.json({ success: true, data: updated });
}
);
export default app;
プログラムからの権限チェック¶
例8: 関数内で権限をチェック¶
import { hasResourcePermission, getUserResourcePermissions } from '../lib/api/auth';
// 特定の権限を持っているかチェック
async function canUserEditProject(userId: string, projectId: string): Promise<boolean> {
return await hasResourcePermission(
userId,
'CIRCLE_PROJECT',
projectId,
['WRITE']
);
}
// ユーザーが持つ全ての権限を取得
async function getUserProjectPermissions(userId: string, projectId: string) {
const permissions = await getUserResourcePermissions(
userId,
'CIRCLE_PROJECT',
projectId
);
console.log('User permissions:', permissions);
// 出力例: ['READ', 'WRITE', 'CHECKIN']
return permissions;
}
// 複数の権限をチェック
async function canUserManageProject(userId: string, projectId: string): Promise<boolean> {
return await hasResourcePermission(
userId,
'CIRCLE_PROJECT',
projectId,
['WRITE', 'MANAGE_MEMBERS', 'MANAGE_PERMISSIONS']
);
}
例9: 条件分岐での使用¶
async function handleProjectUpdate(userId: string, projectId: string, updates: any) {
// 権限に応じて編集可能なフィールドを制限
const permissions = await getUserResourcePermissions(userId, 'CIRCLE_PROJECT', projectId);
const allowedUpdates: any = {};
// READ権限は誰でも持っているので除外
if (permissions.includes('WRITE')) {
// 基本的な編集
allowedUpdates.name = updates.name;
allowedUpdates.location = updates.location;
allowedUpdates.description = updates.description;
}
if (permissions.includes('VIEW_PRIVATE')) {
// 非公開情報の編集
allowedUpdates.memo = updates.memo;
}
if (permissions.includes('APPROVE')) {
// ステータスの変更
allowedUpdates.status = updates.status;
}
if (permissions.includes('DELETE')) {
// アーカイブ
allowedUpdates.isArchived = updates.isArchived;
}
return await prisma.circleProject.update({
where: { id: projectId },
data: allowedUpdates,
});
}
フロントエンドでの使用¶
例10: React フックで権限チェック¶
// hooks/useResourcePermission.ts
import { useState, useEffect } from 'react';
import type { ResourceType, Permission } from '@/lib/api/auth';
export function useResourcePermission(
resourceType: ResourceType,
resourceId: string,
requiredPermissions: Permission[]
) {
const [hasPermission, setHasPermission] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function checkPermission() {
try {
const response = await fetch(
`/api/resource-permissions/check?` +
`resourceType=${resourceType}&` +
`resourceId=${resourceId}&` +
`permissions=${requiredPermissions.join(',')}`
);
const data = await response.json();
setHasPermission(data.hasPermission);
} catch (error) {
console.error('Permission check failed:', error);
setHasPermission(false);
} finally {
setLoading(false);
}
}
checkPermission();
}, [resourceType, resourceId, requiredPermissions]);
return { hasPermission, loading };
}
例11: 権限に応じたUIの表示/非表示¶
// components/CircleProjectActions.tsx
import { useResourcePermission } from '@/hooks/useResourcePermission';
function CircleProjectActions({ projectId }: { projectId: string }) {
const { hasPermission: canEdit } = useResourcePermission(
'CIRCLE_PROJECT',
projectId,
['WRITE']
);
const { hasPermission: canManageMembers } = useResourcePermission(
'CIRCLE_PROJECT',
projectId,
['MANAGE_MEMBERS']
);
const { hasPermission: canApprove } = useResourcePermission(
'CIRCLE_PROJECT',
projectId,
['APPROVE']
);
return (
<div className="action-buttons">
{canEdit && (
<button onClick={handleEdit}>編集</button>
)}
{canManageMembers && (
<button onClick={handleManageMembers}>メンバー管理</button>
)}
{canApprove && (
<button onClick={handleApprove}>承認</button>
)}
</div>
);
}
例12: ユーザーの権限一覧を表示¶
// components/UserPermissions.tsx
import { useState, useEffect } from 'react';
function UserPermissions({ userId, resourceType, resourceId }: Props) {
const [permissions, setPermissions] = useState([]);
useEffect(() => {
async function fetchPermissions() {
const response = await fetch(
`/api/resource-permissions?` +
`userId=${userId}&` +
`resourceType=${resourceType}&` +
`resourceId=${resourceId}`
);
const data = await response.json();
setPermissions(data.permissions);
}
fetchPermissions();
}, [userId, resourceType, resourceId]);
return (
<div className="permissions-list">
<h3>ユーザー権限</h3>
<ul>
{permissions.map((perm) => (
<li key={perm.id}>
<strong>{perm.resourceType}</strong> - {perm.resourceId}
<ul>
{perm.permissions.map((p) => (
<li key={p}>{p}</li>
))}
</ul>
{perm.expiresAt && (
<p>有効期限: {new Date(perm.expiresAt).toLocaleDateString()}</p>
)}
</li>
))}
</ul>
</div>
);
}
例13: 権限付与フォーム¶
// components/GrantPermissionForm.tsx
import { useState } from 'react';
function GrantPermissionForm({ resourceType, resourceId }: Props) {
const [selectedUser, setSelectedUser] = useState('');
const [selectedTemplate, setSelectedTemplate] = useState('');
const [templates, setTemplates] = useState([]);
useEffect(() => {
// ロールテンプレートを取得
async function fetchTemplates() {
const response = await fetch(
`/api/resource-role-templates?resourceType=${resourceType}`
);
const data = await response.json();
setTemplates(data.templates);
}
fetchTemplates();
}, [resourceType]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const response = await fetch('/api/resource-permissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: selectedUser,
resourceType,
resourceId,
roleTemplate: selectedTemplate,
}),
});
if (response.ok) {
alert('権限を付与しました');
}
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>ユーザー</label>
<select value={selectedUser} onChange={(e) => setSelectedUser(e.target.value)}>
<option value="">選択してください</option>
{/* ユーザー一覧 */}
</select>
</div>
<div>
<label>ロール</label>
<select value={selectedTemplate} onChange={(e) => setSelectedTemplate(e.target.value)}>
<option value="">選択してください</option>
{templates.map((t) => (
<option key={t.id} value={t.name}>
{t.name} - {t.description}
</option>
))}
</select>
</div>
<button type="submit">権限を付与</button>
</form>
);
}
高度な使用例¶
例14: バッチ処理で複数ユーザーに権限を付与¶
async function grantPermissionsToMultipleUsers(
userIds: string[],
resourceType: ResourceType,
resourceId: string,
roleTemplate: string
) {
const results = await Promise.allSettled(
userIds.map((userId) =>
fetch('/api/resource-permissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId,
resourceType,
resourceId,
roleTemplate,
}),
})
)
);
const succeeded = results.filter((r) => r.status === 'fulfilled').length;
const failed = results.filter((r) => r.status === 'rejected').length;
console.log(`成功: ${succeeded}件, 失敗: ${failed}件`);
return { succeeded, failed };
}
例15: 権限の移行スクリプト¶
// 既存のProjectMemberから権限を移行
async function migrateProjectMembersToResourcePermissions() {
const circleProjects = await prisma.circleProject.findMany({
include: {
members: {
orderBy: { id: 'asc' },
},
},
});
for (const project of circleProjects) {
for (let i = 0; i < project.members.length; i++) {
const isManager = i === 0; // 最初のメンバーがマネージャー
const roleTemplate = isManager ? 'Manager' : 'Member';
await fetch('/api/resource-permissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: project.members[i].userId,
resourceType: 'CIRCLE_PROJECT',
resourceId: project.id,
roleTemplate,
}),
});
}
}
console.log('Migration completed');
}
デバッグとテスト¶
例16: 権限チェックのデバッグ¶
# 特定のユーザーの権限を確認
curl -X GET "http://localhost:3000/api/resource-permissions/check?\
resourceType=CIRCLE_PROJECT&\
resourceId=circle-project-uuid&\
permissions=READ,WRITE,APPROVE" \
-H "Authorization: Bearer USER_TOKEN"
レスポンス例:
{
"hasPermission": false,
"userPermissions": ["READ", "WRITE"],
"requiredPermissions": ["READ", "WRITE", "APPROVE"],
"missingPermissions": ["APPROVE"]
}
例17: 期限切れ権限のクリーンアップ¶
async function cleanupExpiredPermissions() {
const expired = await prisma.resourcePermission.deleteMany({
where: {
expiresAt: {
lt: new Date(),
},
},
});
console.log(`${expired.count}件の期限切れ権限を削除しました`);
}
まとめ¶
リソースレベル権限システムを使用することで、きめ細かいアクセス制御が可能になります。
主なポイント:
- 柔軟な権限管理: リソースごとに異なる権限を付与できる
- ロールテンプレート: よく使う権限セットを簡単に適用
- 期限付き権限: 一時的なアクセス権限を設定可能
- 後方互換性: FullAccessAdminは引き続き全権限を保持
詳細は以下のドキュメントを参照してください: - API_SPECIFICATION.md - API仕様 - MIGRATION_GUIDE.md - 移行ガイド