User Context
Zeltは、認証済みユーザーへアクセスし管理するための、request-scopedな関数を提供します。
Core Functions
| 関数 | 説明 |
|---|---|
setUser(user, roles) | 認証済みユーザーを設定する(middlewareで呼び出す) |
currentUser() | 現在のユーザーを取得する(未認証なら undefined を返す) |
currentRoles() | 現在のユーザーのroleを取得する(未認証なら [] を返す) |
ユーザーを設定する
認証情報を検証した後、認証middleware内で setUser() を呼び出します。
@Middleware
export class AuthMiddleware {
async use(next: Next, req = request()): Promise<Response | undefined> {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (token) {
const payload = await verifyToken(token);
setUser(
{ id: payload.sub, name: payload.name, email: payload.email },
payload.roles
);
}
await next();
return undefined;
}
}
Parameters
- user — 認証済みユーザーを表す任意のオブジェクト
- roles — role文字列の配列(例:
['admin', 'user'])
ユーザーへアクセスする
Route Handler内で
認証済みユーザーへアクセスするには currentUser() を使います。
@Controller('/profile')
class ProfileController {
@Get('/me')
me() {
const user = currentUser();
const roles = currentRoles();
if (!user) {
throw new HTTPException(401, { message: 'Not authenticated' });
}
return { user, roles, isAdmin: roles.includes('admin') };
}
}
デフォルト引数を使う
ハンドラーのシグネチャをすっきりさせるには、デフォルト引数を使います。
@Controller('/profile')
class ProfileController {
@Get('/me')
me(user = currentUser()) {
return user;
}
}
Typed Access
currentUser() は常に Record<string, unknown> | undefined を返します。setUser() に渡した形は型レベルでは追跡されないため、特定のフィールドを読み取るには手動でのアサーションや絞り込みが必要です。
handlerで利用できる、具体的に絞り込まれた型の値が必要な場合は、代わりにmiddleware経由で提供します。Next<T> に宣言して next(value) に渡し、middlewareValue(M) で読み取ります。詳細はMiddleware Valuesを参照してください。
User設計のBest Practices
最小限に保つ
ハンドラーで必要なフィールドだけを含めます。データベースレコード全体をコピーしないでください。
// ✅ Good — 最小限のuser
setUser({ id: '123', name: 'Alice' }, ['user']);
// ❌ Avoid — レコード全体をコピーする
setUser({
id: '123',
name: 'Alice',
email: 'alice@example.com',
passwordHash: '...', // 機微なデータは含めない
createdAt: new Date(),
updatedAt: new Date(),
preferences: {},
// ...さらに20個のフィールド
}, ['user']);
必要な時に追加データを取得する
特定のハンドラー内で、ユーザーIDを使ってより多くのデータを取得します。
@Controller('/settings')
class SettingsController {
constructor(private userRepo = inject(UserRepository)) {}
@Authorized()
@Get('/')
async getSettings() {
const user = currentUser();
if (!isSessionUser(user)) return;
const fullUser = await this.userRepo.findById(user.id);
return { preferences: fullUser.preferences };
}
}
Roleの粒度を考える
roleはシンプルな文字列にすべきです。複雑な権限ロジックはサービス層に置きます。
// ✅ Good — シンプルなrole
type GoodRoles = ('admin' | 'editor' | 'viewer')[];
// ❌ Avoid — 過度に具体的なrole
type BadRoles = ('can_edit_posts' | 'can_delete_posts' | 'can_view_analytics')[];
きめ細かい権限が必要な場合は、サービス層でroleをチェックします。
function canEdit(post: Post): boolean {
const user = currentUser();
const roles = currentRoles();
if (roles.includes('admin')) return true;
if (roles.includes('editor') && isSessionUser(user) && post.authorId === user.id) return true;
return false;
}