メインコンテンツまでスキップ

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;
}