Compare commits

..

3 Commits

2 changed files with 31 additions and 23 deletions

View File

@ -13,11 +13,13 @@
- **contact.html**: Resend API 経由に切り替え済み・送受信テスト完了2026-04-21
- **Stripe 本番化時**: index.html・index-b.html・index-c.html・index-d.html の 4ファイルにある `test_9B67sEbN3fowfMW4jwenS00` を本番 Payment Link URL に一括差し替えCursor 指摘 2026-04-22
## Together legacy path 廃止2026-04-22 完了
## Together 認証設計2026-04-22 確定
- Docker ログで legacy hit 0件を確認の上、JWT 必須化済みserver.js `togetherEnsureMember`
- JWT なしリクエストは 401 を返す
- Gemini 2.5-flash 503 フォールバック(→ 2.0-flash`archiveShare` + `rearchive` に追加済み
- Together は Posimai アカウント不要のため、JWT なし username 認証を継続維持する
- JWT あり: user_id + username で strict チェック
- JWT なし: username のみで照合Eiji 等 Posimai 外メンバー向け)
- invite_code はメンバー認証済みであれば JWT 有無に関わらず返す
- Gemini 2.5-flash 503 フォールバック(→ 2.0-flash`archiveShare` + `rearchive` に追加済み2026-04-22
## 次にやること(優先順)

View File

@ -807,24 +807,33 @@ async function togetherEnsureMember(pool, res, groupId, username, jwtUserId) {
return false;
}
try {
if (!jwtUserId) {
res.status(401).json({ error: '認証が必要です' });
if (jwtUserId) {
const strict = await pool.query(
`SELECT 1 FROM together_members m
WHERE m.group_id = $1 AND (
m.user_id = $2
OR (
(m.user_id IS NULL OR btrim(COALESCE(m.user_id, '')) = '')
AND m.username = ANY($3::text[])
)
)`,
[gidNum, jwtUserId, usernames]
);
if (strict.rows.length > 0) return true;
res.status(403).json({ error: 'グループのメンバーではありません' });
return false;
}
const strict = await pool.query(
`SELECT 1 FROM together_members m
WHERE m.group_id = $1 AND (
m.user_id = $2
OR (
(m.user_id IS NULL OR btrim(COALESCE(m.user_id, '')) = '')
AND m.username = ANY($3::text[])
)
)`,
[gidNum, jwtUserId, usernames]
// JWT なし: username のみで照合Together は Posimai アカウント不要のため継続許容)
const primaryUsername = usernames[0];
const legacyOnly = await pool.query(
'SELECT 1 FROM together_members WHERE group_id=$1 AND username=$2',
[gidNum, primaryUsername]
);
if (strict.rows.length > 0) return true;
res.status(403).json({ error: 'グループのメンバーではありません' });
return false;
if (legacyOnly.rows.length === 0) {
res.status(403).json({ error: 'グループのメンバーではありません' });
return false;
}
return true;
} catch (e) {
console.error('[Together] togetherEnsureMember', e.message);
res.status(500).json({ error: 'Internal server error' });
@ -2760,7 +2769,6 @@ ${excerpt}
});
// GET /together/groups/:groupId — グループ情報(メンバーのみ)
// invite_code は JWT 認証済みの場合のみ返す(レガシー ?u= のみでは返さない)
r.get('/together/groups/:groupId', async (req, res) => {
if (!/^[a-zA-Z0-9_-]+$/.test(req.params.groupId)) return res.status(400).json({ error: 'invalid groupId' });
const username = normalizeTogetherUsername(req.query.u);
@ -2769,9 +2777,7 @@ ${excerpt}
if (!(await togetherEnsureMember(pool, res, req.params.groupId, username, jwtUserId))) return;
const result = await pool.query('SELECT id, name, invite_code, created_at FROM together_groups WHERE id=$1', [req.params.groupId]);
if (result.rows.length === 0) return res.status(404).json({ error: 'グループが見つかりません' });
const group = result.rows[0];
if (!jwtUserId) delete group.invite_code;
res.json(group);
res.json(result.rows[0]);
} catch (e) {
res.status(500).json({ error: 'Internal server error' });
}