データモデル
newland-core は Cloudflare D1(SQLite)と Cloudflare KV を使用します。
D1 テーブル定義(USER_DB)
tenants
マルチテナントの組織情報を管理するテーブル。
| カラム | 型 | 制約 | 説明 |
|---|---|---|---|
id | TEXT | PK | テナント ID(UUID) |
slug | TEXT | UNIQUE NOT NULL | URL フレンドリーな識別子(例: newland-studio) |
display_name | TEXT | NOT NULL | 表示名 |
is_active | INTEGER | NOT NULL DEFAULT 1 | 有効フラグ(1=有効, 0=無効) |
created_at | INTEGER | NOT NULL | Unix タイムスタンプ (ms) |
インデックス: idx_tenants_slug (slug)
users
テナントに紐づくユーザー情報。
| カラム | 型 | 制約 | 説明 |
|---|---|---|---|
id | TEXT | PK | ユーザー ID(UUID) |
tenant_id | TEXT | FK → tenants.id | 所属テナント |
email | TEXT | UNIQUE (tenant_id, email) | メールアドレス(テナント内ユニーク) |
password_hash | TEXT | NOT NULL | bcrypt ハッシュ |
display_name | TEXT | NULL | 表示名 |
role | TEXT | NOT NULL DEFAULT 'member' | ロール(owner / admin / member) |
is_active | INTEGER | NOT NULL DEFAULT 1 | 有効フラグ |
created_at | INTEGER | NOT NULL | Unix タイムスタンプ (ms) |
updated_at | INTEGER | NOT NULL | Unix タイムスタンプ (ms) |
last_login_at | INTEGER | NULL | 最終ログイン日時 |
インデックス:
idx_users_tenant_email(tenant_id, email)idx_users_tenant_id(tenant_id)
refresh_tokens
発行済みリフレッシュトークンの管理。
| カラム | 型 | 制約 | 説明 |
|---|---|---|---|
id | TEXT | PK | トークン ID |
user_id | TEXT | FK → users.id | 発行ユーザー |
tenant_id | TEXT | NOT NULL | テナント ID |
token_hash | TEXT | NOT NULL | トークンのハッシュ値 |
issued_at | INTEGER | NOT NULL | 発行日時 (Unix ms) |
expires_at | INTEGER | NOT NULL | 有効期限 (Unix ms) |
revoked_at | INTEGER | NULL | 無効化日時(NULL=有効) |
user_agent | TEXT | NULL | クライアント User-Agent |
ip_address | TEXT | NULL | クライアント IP |
インデックス:
idx_refresh_tokens_user_tenant(user_id, tenant_id)idx_refresh_tokens_token_hash(token_hash)
KV スキーマ(SESSION_KV)
| キーパターン | 値(JSON) | TTL | 用途 |
|---|---|---|---|
session:{jti} | SessionPayload | 3600秒 | アクセストークン有効セッション |
denylist:{jti} | "1" | トークン残存期間 | ログアウト済みトークンの拒否リスト |
tenant:slug:{slug} | { id: string, isActive: boolean } | 3600秒 | テナント解決キャッシュ |
SessionPayload 型
typescript
interface SessionPayload {
userId: string
tenantId: string
role: 'owner' | 'admin' | 'member'
email: string
jti: string
expiresAt: number
}KV 操作フロー
ログイン:
WRITE session:{jti} = SessionPayload (TTL: 3600s)
ログアウト:
DELETE session:{jti}
WRITE denylist:{jti} = "1" (TTL: 残存秒数)
トークン検証:
READ denylist:{jti} → 存在すれば revoked
READ session:{jti} → null なら expired
テナント解決:
READ tenant:slug:{slug} → miss なら D1 から取得して WRITEService Binding (RPC)
他の Cloudflare Worker から newland-core をゼロコピー RPC で呼び出せます。
wrangler.toml 設定(呼び出し元 Worker)
toml
[[services]]
binding = "CORE_RPC"
service = "newland-core"
entrypoint = "CoreRpc"使用例
typescript
interface Env {
CORE_RPC: Service<CoreRpc>
}
// トークン検証
const result = await env.CORE_RPC.verifyToken({ token: accessToken })
if (!result.valid) {
return c.json({ error: result.reason }, 401)
}
// result.userId, result.tenantId, result.role, result.email が利用可能
// ユーザープロフィール取得
const user = await env.CORE_RPC.getUserProfile(userId, tenantId)CoreRpc メソッド一覧
| メソッド | 引数 | 戻り値 | 説明 |
|---|---|---|---|
verifyToken(req) | { token: string } | VerifyTokenResponse | JWT 検証 + セッション確認 |
getUserProfile(userId, tenantId) | string, string | User | null | D1 からユーザー取得 |
typescript
type VerifyTokenResponse =
| { valid: true; userId: string; tenantId: string; role: 'owner' | 'admin' | 'member'; email: string }
| { valid: false; reason: 'expired' | 'revoked' | 'malformed' | 'tenant_mismatch' }