마이그레이션 및 보안 수정
TaxBaik CI/CD / build-and-deploy (push) Failing after 15s

- MigrationRunner: 이미 존재하는 테이블에 대한 "relation already exists" 오류 처리
- V002, V003 마이그레이션: ON CONFLICT DO NOTHING으로 멱등성 보장
- Web, Admin Program.cs: app.UseAntiforgery() 미들웨어 추가 (anti-forgery 토큰 검증)

변경사항:
- 마이그레이션 재실행 시에도 안전하게 처리
- 폼 제출 시 CSRF 공격 방지
- 관리자 로그인 페이지 405 에러 해결

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 16:46:36 +09:00
parent 701a841279
commit e7e01d0cd8
5 changed files with 21 additions and 4 deletions
+1
View File
@@ -44,6 +44,7 @@ app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseAntiforgery();
app.MapRazorComponents<TaxBaik.Admin.Components.App>() app.MapRazorComponents<TaxBaik.Admin.Components.App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode();
@@ -122,6 +122,17 @@ public class MigrationRunner
Console.WriteLine($"✓ Migration {migration.Version} executed"); Console.WriteLine($"✓ Migration {migration.Version} executed");
} }
catch (Npgsql.PostgresException pgEx) when (pgEx.SqlState == "42P07") // relation already exists
{
// Already executed previously; mark as done
Console.WriteLine($" Migration {migration.Version} already applied");
using var insertCmd = conn.CreateCommand();
insertCmd.CommandText =
"INSERT INTO schema_migrations (version, description) VALUES (@version, @description) ON CONFLICT (version) DO NOTHING;";
insertCmd.Parameters.AddWithValue("@version", migration.Version);
insertCmd.Parameters.AddWithValue("@description", migration.Description);
await insertCmd.ExecuteNonQueryAsync();
}
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"✗ Migration {migration.Version} failed: {ex.Message}"); Console.WriteLine($"✗ Migration {migration.Version} failed: {ex.Message}");
+1
View File
@@ -29,6 +29,7 @@ app.UsePathBase("/taxbaik");
app.UseResponseCompression(); app.UseResponseCompression();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.UseAntiforgery();
if (!app.Environment.IsDevelopment()) if (!app.Environment.IsDevelopment())
{ {
+4 -2
View File
@@ -5,11 +5,13 @@ INSERT INTO categories (name, slug, sort_order) VALUES
('부동산 세금', 'real-estate-tax', 2), ('부동산 세금', 'real-estate-tax', 2),
('종합소득세', 'income-tax', 3), ('종합소득세', 'income-tax', 3),
('부가가치세', 'vat', 4), ('부가가치세', 'vat', 4),
('가족자산·증여', 'family-asset', 5); ('가족자산·증여', 'family-asset', 5)
ON CONFLICT (slug) DO NOTHING;
INSERT INTO site_settings (key, value) VALUES INSERT INTO site_settings (key, value) VALUES
('site.title', '백원숙 세무회계 | 사업자·부동산·증여 세무 상담'), ('site.title', '백원숙 세무회계 | 사업자·부동산·증여 세무 상담'),
('site.description', '사업자 기장, 부동산 양도세·증여세, 종합소득세 전문 상담. 온라인 맞춤 상담 제공.'), ('site.description', '사업자 기장, 부동산 양도세·증여세, 종합소득세 전문 상담. 온라인 맞춤 상담 제공.'),
('kakao.channel.url', ''), ('kakao.channel.url', ''),
('phone.main', ''), ('phone.main', ''),
('consultation.fee.text','상담료 7만~20만 원, 계약 체결 시 일부 차감'); ('consultation.fee.text','상담료 7만~20만 원, 계약 체결 시 일부 차감')
ON CONFLICT (key) DO NOTHING;
@@ -1,7 +1,8 @@
-- 초기 관리자 계정 (비밀번호: admin123) -- 초기 관리자 계정 (비밀번호: admin123)
-- bcrypt hash: $2a$11$N9qo8uLOickgx2ZMRZoMye (실제 환경에서는 강력한 암호 사용) -- bcrypt hash: $2a$11$N9qo8uLOickgx2ZMRZoMye (실제 환경에서는 강력한 암호 사용)
INSERT INTO admin_users (username, password_hash, created_at) INSERT INTO admin_users (username, password_hash, created_at)
VALUES ('admin', '$2a$11$N9qo8uLOickgx2ZMRZoMye6IjfQTp5emXyqhT3jrDZWCqYIxJkAOq', NOW()); VALUES ('admin', '$2a$11$N9qo8uLOickgx2ZMRZoMye6IjfQTp5emXyqhT3jrDZWCqYIxJkAOq', NOW())
ON CONFLICT (username) DO NOTHING;
-- 초기 블로그 포스트 5개 -- 초기 블로그 포스트 5개
INSERT INTO blog_posts (title, content, slug, category_id, tags, author_id, published_at, is_published, seo_title, seo_description, created_at, updated_at) INSERT INTO blog_posts (title, content, slug, category_id, tags, author_id, published_at, is_published, seo_title, seo_description, created_at, updated_at)
@@ -75,4 +76,5 @@ VALUES
'증여세를 절감하는 전략적인 증여 방법을 소개합니다.', '증여세를 절감하는 전략적인 증여 방법을 소개합니다.',
NOW(), NOW(),
NOW() NOW()
); )
ON CONFLICT (slug) DO NOTHING;