-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
249 lines (208 loc) · 9.51 KB
/
Copy pathProgram.cs
File metadata and controls
249 lines (208 loc) · 9.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContextIQMvcWebApi.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Microsoft.Extensions.AI;
using Azure.AI.OpenAI;
using Azure;
using Azure.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using ContextIQMvcWebApi.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
// Forgot password email sender service
builder.Services.AddTransient<IEmailSender, EmailSender>();
// Bind SendGrid settings to AuthMessageSenderOptions from the "SendGrid" section
builder.Services.Configure<AuthMessageSenderOptions>(
builder.Configuration.GetSection("SendGrid")
);
// Chat Client Configuration/MCP Configuration
var endpoint = builder.Configuration["MCP:Endpoint"];
var apiKey = builder.Configuration["MCP:ApiKey"];
var model = builder.Configuration["MCP:ModelName"];
builder.Services.AddChatClient(services =>
new ChatClientBuilder(
(
!string.IsNullOrEmpty(apiKey)
? new AzureOpenAIClient(new Uri(endpoint!), new AzureKeyCredential(apiKey))
: new AzureOpenAIClient(new Uri(endpoint!), new DefaultAzureCredential())
).GetChatClient(model).AsIChatClient()
)
.UseFunctionInvocation()
.Build());
// Swagger for API testing and documentation
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Configure JSON serialization for minimal APIs
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
options.SerializerOptions.WriteIndented = true;
});
// Database Configuration
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? builder.Configuration.GetConnectionString("AZURE_MYSQL_CONNECTIONSTRING");
if (string.IsNullOrEmpty(connectionString))
{
// Fallback for development or if connection string is not set
connectionString = "Server=localhost;Port=3306;Database=ChatApp;User=root;Password=;";
builder.Logging.AddConsole().SetMinimumLevel(LogLevel.Warning);
Console.WriteLine("Warning: Using default connection string. Configure connection string in Azure.");
}
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
// Use a specific MySQL version instead of AutoDetect to avoid startup connection issues
options.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 21)));
});
// Catch EF Core database errors in development
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
// Identity + Authentication + Authorization Configuration
// Configure authentication (Cookies for UI + Bearer tokens for API endpoints)
builder.Services.AddAuthentication(options =>
{
// Default everything to Bearer for APIs and SignalR
options.DefaultAuthenticateScheme = IdentityConstants.BearerScheme;
options.DefaultChallengeScheme = IdentityConstants.BearerScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "AIChatAuth";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // require HTTPS
options.Cookie.SameSite = SameSiteMode.Lax; // or Strict depending on SPA cross-site needs
options.ExpireTimeSpan = TimeSpan.FromHours(8); // session duration
options.SlidingExpiration = true;
options.LoginPath = "/account/login"; // optional
options.LogoutPath = "/account/logout";
})
.AddBearerToken(IdentityConstants.BearerScheme);
// Configure ASP.NET Core Identity with roles and API endpoints
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
options.Stores.MaxLengthForKeys = 128; // Maximum length for key strings
// Password settings
// options.Password.RequireDigit = true; // Must include at least one number
// options.Password.RequireLowercase = true; // Must include at least one lowercase letter
// options.Password.RequireUppercase = true; // Must include at least one uppercase letter
// options.Password.RequireNonAlphanumeric = true; // Must include at least one special character
// options.Password.RequiredLength = 8; // Minimum length
// options.Password.RequiredUniqueChars = 1; // At least one unique character
options.Password.RequireDigit = false; // Must include at least one number
options.Password.RequireLowercase = false; // Must include at least one lowercase letter
options.Password.RequireUppercase = false; // Must include at least one uppercase letter
options.Password.RequireNonAlphanumeric = false; // Must include at least one special character
options.Password.RequiredLength = 3; // Minimum length
options.Password.RequiredUniqueChars = 1; // At least one unique character
})
.AddEntityFrameworkStores<ApplicationDbContext>() // Use EF Core for storage
.AddRoles<IdentityRole>() // Enable role-based access
.AddDefaultUI() // Enable default Razor UI (login, register)
.AddDefaultTokenProviders() // Enable password reset and email confirmation tokens
.AddApiEndpoints(); // Adds /login, /register, /logout, etc.
// Add custom authorization policy for API endpoints
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiPolicy", policy =>
policy.RequireAuthenticatedUser()
.AddAuthenticationSchemes(IdentityConstants.BearerScheme));
});
// MVC, Razor Pages, and Email Services
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
// CORS Configuration - MUST specify exact origins when using AllowCredentials
// Azure App Service CORS settings should be DISABLED in the Azure portal
// to avoid conflicts with this policy
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins(
"https://aichatapp-61vl.onrender.com", // Render frontend
"https://chatapptermproject-ah-fcbufpg5dsc8b4g3.canadacentral-01.azurewebsites.net", // Backend itself
"http://localhost:3000",
"http://localhost:5173",
"http://localhost:5157"
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials() // Required for SignalR with auth
.WithExposedHeaders("X-Usage-Warning"); // Expose custom header to frontend
});
});
// SignalR Configuration
builder.Services.AddSignalR();
// Build the application
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
app.UseSwagger();
app.UseSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json", "ISATermProjectBackend");
options.RoutePrefix = "";
});
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts(); // Enable HSTS for production
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("AllowFrontend"); // Allow requests from specified frontend
app.UseAuthentication(); // Enable authentication middleware
app.UseAuthorization(); // Enable authorization middleware
// Add request counting middleware (must come after authentication)
app.UseMiddleware<ContextIQMvcWebApi.Middleware.RequestCountingMiddleware>();
app.MapStaticAssets();
// Route Mapping
// Default controller route
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
// Map Razor Pages (for Identity UI)
app.MapRazorPages()
.WithStaticAssets();
// Map Identity API endpoints (/login, /register, etc.)
app.MapIdentityApi<IdentityUser>();
// Map SignalR hubs
app.MapHub<ContextIQMvcWebApi.Services.ChatHub>("/chathub");
// Add a simple health check endpoint
app.MapGet("/health", () => "Healthy - App is running!");
// Database seeding (creates admin role and default users)
try
{
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Starting database migration and seeding...");
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
logger.LogInformation("Database migration completed successfully.");
var userMgr = services.GetRequiredService<UserManager<IdentityUser>>();
var roleMgr = services.GetRequiredService<RoleManager<IdentityRole>>();
var configuration = services.GetRequiredService<IConfiguration>();
// Initialize Identity seed data (creates admin@admin.com and roles)
IdentitySeedData.Initialize(context, userMgr, roleMgr, configuration).Wait();
logger.LogInformation("Database seeding completed successfully.");
}
}
catch (Exception ex)
{
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred during database migration or seeding. App will continue startup.");
// Don't let database issues prevent app startup
}
// Temporary test - remove after verifying
// await ContextIQMvcWebApi.Services.SendGridExample.SendTestEmailAsync(
// apiKeyEnvVar: "SENDGRID_API_KEY",
// fromEmail: "burnermax247@gmail.com",
// fromName: "Your Name",
// toEmail: "burnermax247@gmail.com",
// toName: "You");
// Run the application
app.Run();