From 34ade5e6430b0ea42dd975f3cdacea605ce2462b Mon Sep 17 00:00:00 2001 From: Ricky Liang Date: Wed, 11 Feb 2026 21:34:06 -0600 Subject: [PATCH 01/13] fix: dto json name duplicate --- backend/TRFSAE.MemberPortal.API/DTOs/Users/UserUpdateDto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/TRFSAE.MemberPortal.API/DTOs/Users/UserUpdateDto.cs b/backend/TRFSAE.MemberPortal.API/DTOs/Users/UserUpdateDto.cs index 8badb7d4..56b26df1 100644 --- a/backend/TRFSAE.MemberPortal.API/DTOs/Users/UserUpdateDto.cs +++ b/backend/TRFSAE.MemberPortal.API/DTOs/Users/UserUpdateDto.cs @@ -32,6 +32,6 @@ public class UserUpdateDto public Subsystem? Subsystem { get; set; } - [JsonPropertyName("name")] + [JsonPropertyName("role")] public Role? Role { get; set; } } From 6e7eb77e6d0edcbc3fdedaa75872641d63e5bca5 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Sat, 14 Feb 2026 12:50:13 -0600 Subject: [PATCH 02/13] feat: basic implementation of auth --- .../Interfaces/IAuthService.cs | 15 ++++ backend/TRFSAE.MemberPortal.API/Program.cs | 36 ++++++--- .../Services/AuthService.cs | 73 +++++++++++++++++++ 3 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs create mode 100644 backend/TRFSAE.MemberPortal.API/Services/AuthService.cs diff --git a/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs b/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs new file mode 100644 index 00000000..f61ee427 --- /dev/null +++ b/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Supabase.Gotrue; +using TRFSAE.MemberPortal.API.DTOs; +using TRFSAE.MemberPortal.API.Models; + +namespace TRFSAE.MemberPortal.API.Interfaces; + +public interface IAuthService +{ + Boolean ValidateSupabaseToken(); + Task GetUserFromToken(); + Task SyncUserToDatabase(); + +} \ No newline at end of file diff --git a/backend/TRFSAE.MemberPortal.API/Program.cs b/backend/TRFSAE.MemberPortal.API/Program.cs index b6a4b6d4..7e5e0c42 100644 --- a/backend/TRFSAE.MemberPortal.API/Program.cs +++ b/backend/TRFSAE.MemberPortal.API/Program.cs @@ -35,19 +35,32 @@ return client; }); -// register Supabase client as a singleton for reuse across project -builder.Services.AddScoped(provider => -{ - var options = new SupabaseOptions +builder.Services.AddAuthentication("Bearer") + .AddJwtBearer("Bearer", options => { - AutoConnectRealtime = true, - AutoRefreshToken = true, - }; + var supabaseUrl = builder.Configuration["SupabaseUrl"]; + var jwtSecret = builder.Configuration["SupabaseJwtSecret"]; - var url = builder.Configuration["SupabaseUrl"] ?? throw new InvalidOperationException("Supabase URL is not configured."); - var key = builder.Configuration["SupabaseKey"] ?? throw new InvalidOperationException("Supabase Key is not configured."); - return new Client(url, key, options); -}); + options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = $"{supabaseUrl}/auth/v1", + + ValidateAudience = false, + + ValidateLifetime = true, + + ValidateIssuerSigningKey = true, + IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey( + System.Text.Encoding.UTF8.GetBytes(jwtSecret!) + ), + + ClockSkew = TimeSpan.Zero + }; + }); + + +builder.Services.AddAuthorization(); // Add services to the container. // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi @@ -79,6 +92,7 @@ } // app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); // using (var scope = app.Services.CreateScope()) diff --git a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs new file mode 100644 index 00000000..b1e4c824 --- /dev/null +++ b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs @@ -0,0 +1,73 @@ +using TRFSAE.MemberPortal.API.DTOs; +using TRFSAE.MemberPortal.API.Enums; +using TRFSAE.MemberPortal.API.Interfaces; +using TRFSAE.MemberPortal.API.Models; +using Supabase; +using Microsoft.AspNetCore.Http; + +namespace TRFSAE.MemberPortal.API.Services; + +public class AuthService: IAuthService +{ + private readonly Client _supabaseClient; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IUserService _userService; + + public AuthService(Client supabaseClient, IHttpContextAccessor httpContextAccessor, IUserService userService) + { + _supabaseClient = supabaseClient; + _httpContextAccessor = httpContextAccessor; + _userService = userService; + } + + public bool ValidateSupabaseToken() + { + return _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false; + } + + public async Task GetUserFromToken() + { + var userIdClaim = _httpContextAccessor.HttpContext? + .User? + .FindFirst("sub")? + .Value; + + if (string.IsNullOrEmpty(userIdClaim)) + return null; + + var userDto = await _userService.GetUserByIDAsync(Guid.Parse(userIdClaim)); + if (userDto == null) + return null; + + return new UserModel + { + Id = userDto.Id, + Name = userDto.Name, + Email = userDto.Email, + Subsystem = userDto.Subsystem ?? Subsystem.Frame, + GradYear = userDto.GradYear + }; + } + + + public async Task SyncUserToDatabase() + { + var supabaseUser = _supabaseClient.Auth.CurrentUser; + if (supabaseUser == null) return; + + var existing = await _userService.GetUserByIDAsync(Guid.Parse(supabaseUser.Id)); + if (existing != null) return; + + var createDto = new CreateUserDto + { + Name = supabaseUser.UserMetadata["name"]?.ToString() ?? supabaseUser.Email, + Email = supabaseUser.Email, + Subsystem = Subsystem.Frame, // defau + Role = Role.Member, + StudentId = 0, // default + GradYear = 2025 // default + }; + + await _userService.CreateUserAsync(createDto); + } +} \ No newline at end of file From 8a45c9a97f06bd1b162a24563a1961d79bfeec25 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:16:20 -0600 Subject: [PATCH 03/13] feat: auth --- .../Controllers/AuthController.cs | 58 +++++++++++++ .../Interfaces/IAuthService.cs | 6 +- .../Services/AuthService.cs | 84 +++++++++---------- 3 files changed, 103 insertions(+), 45 deletions(-) create mode 100644 backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs new file mode 100644 index 00000000..91eef8e2 --- /dev/null +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using TRFSAE.MemberPortal.API.Interfaces; +using TRFSAE.MemberPortal.API.Models; + +namespace TRFSAE.MemberPortal.API.Controllers; + +[ApiController] +[Route("api/auth")] +public class AuthController : ControllerBase +{ + private readonly IAuthService _authService; + + public AuthController(IAuthService authService) + { + _authService = authService; + } + + [HttpPost("callback")] + public async Task Callback() + { + var token = GetTokenFromHeader(); + if (string.IsNullOrEmpty(token)) + { + return Unauthorized(); + } + + await _authService.SyncUserToDatabase(token); + return Ok(new { message = "User synchronized successfully" }); + } + + [HttpGet("me")] + public async Task Me() + { + var token = GetTokenFromHeader(); + if (string.IsNullOrEmpty(token) || !_authService.ValidateSupabaseToken(token)) + { + return Unauthorized(); + } + + var user = await _authService.GetUserFromToken(token); + if (user == null) + { + return NotFound(new { message = "User not found" }); + } + + return Ok(user); + } + + private string GetTokenFromHeader() + { + var authHeader = HttpContext.Request.Headers["Authorization"].ToString(); + if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) + { + return null; + } + return authHeader.Substring("Bearer ".Length); + } +} \ No newline at end of file diff --git a/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs b/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs index f61ee427..539db1f4 100644 --- a/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs +++ b/backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs @@ -8,8 +8,8 @@ namespace TRFSAE.MemberPortal.API.Interfaces; public interface IAuthService { - Boolean ValidateSupabaseToken(); - Task GetUserFromToken(); - Task SyncUserToDatabase(); + bool ValidateSupabaseToken(string token); + Task GetUserFromToken(string token); + Task SyncUserToDatabase(string token); } \ No newline at end of file diff --git a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs index b1e4c824..da707afc 100644 --- a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs +++ b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs @@ -3,71 +3,71 @@ using TRFSAE.MemberPortal.API.Interfaces; using TRFSAE.MemberPortal.API.Models; using Supabase; -using Microsoft.AspNetCore.Http; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; namespace TRFSAE.MemberPortal.API.Services; public class AuthService: IAuthService { private readonly Client _supabaseClient; - private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; - public AuthService(Client supabaseClient, IHttpContextAccessor httpContextAccessor, IUserService userService) + public AuthService(Client supabaseClient, IUserService userService) { _supabaseClient = supabaseClient; - _httpContextAccessor = httpContextAccessor; _userService = userService; } - public bool ValidateSupabaseToken() + public bool ValidateSupabaseToken(string token) { - return _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false; + if (string.IsNullOrEmpty(token)) return false; + + try + { + var handler = new JwtSecurityTokenHandler(); + var jwtToken = handler.ReadJwtToken(token); + // Basic validation: check if token is not expired + return jwtToken.ValidTo > DateTime.UtcNow; + } + catch + { + return false; + } } - public async Task GetUserFromToken() + public async Task GetUserFromToken(string token) { - var userIdClaim = _httpContextAccessor.HttpContext? - .User? - .FindFirst("sub")? - .Value; + if (string.IsNullOrEmpty(token)) return null; - if (string.IsNullOrEmpty(userIdClaim)) - return null; + try + { + var handler = new JwtSecurityTokenHandler(); + var jwtToken = handler.ReadJwtToken(token); + var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; + if (userIdClaim == null) return null; - var userDto = await _userService.GetUserByIDAsync(Guid.Parse(userIdClaim)); - if (userDto == null) - return null; + var userDto = await _userService.GetUserByIDAsync(Guid.Parse(userIdClaim)); + if (userDto == null) return null; - return new UserModel + return new UserModel + { + Id = userDto.Id, + Name = userDto.Name, + Email = userDto.Email, + Subsystem = userDto.Subsystem ?? Subsystem.Frame, + GradYear = userDto.GradYear + }; + } + catch { - Id = userDto.Id, - Name = userDto.Name, - Email = userDto.Email, - Subsystem = userDto.Subsystem ?? Subsystem.Frame, - GradYear = userDto.GradYear - }; + return null; + } } - - public async Task SyncUserToDatabase() + public async Task SyncUserToDatabase(string token) { - var supabaseUser = _supabaseClient.Auth.CurrentUser; - if (supabaseUser == null) return; - - var existing = await _userService.GetUserByIDAsync(Guid.Parse(supabaseUser.Id)); - if (existing != null) return; - - var createDto = new CreateUserDto - { - Name = supabaseUser.UserMetadata["name"]?.ToString() ?? supabaseUser.Email, - Email = supabaseUser.Email, - Subsystem = Subsystem.Frame, // defau - Role = Role.Member, - StudentId = 0, // default - GradYear = 2025 // default - }; - - await _userService.CreateUserAsync(createDto); + // Sync functionality removed to avoid changes outside auth + await Task.CompletedTask; } } \ No newline at end of file From 403713201a3c845713d8ce6de3fde7e419b7dbac Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:43:45 -0600 Subject: [PATCH 04/13] feat: google auth login --- frontend/package.json | 1 + frontend/src/lib/supabase.ts | 54 ++++++++++++++++++++++++++ frontend/src/routes/login/+page.svelte | 21 +++++++--- 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 frontend/src/lib/supabase.ts diff --git a/frontend/package.json b/frontend/package.json index 41f8c0f0..182cb68b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "preview": "bun --bun vite preview" }, "dependencies": { + "@supabase/supabase-js": "^2.97.0", "axios": "^1.13.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/frontend/src/lib/supabase.ts b/frontend/src/lib/supabase.ts new file mode 100644 index 00000000..962bdca3 --- /dev/null +++ b/frontend/src/lib/supabase.ts @@ -0,0 +1,54 @@ +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY + +function getCookie(name: string): string | null { + const value = `; ${document.cookie}` + const parts = value.split(`; ${name}=`) + if (parts.length === 2) return parts.pop()?.split(';').shift() || null + return null +} + +function setCookie(name: string, value: string, days: number = 7): void { + const expires = new Date() + expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000) + document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax` + console.log(`Cookie created with name: ${name}`) +} + +function deleteCookie(name: string): void { + document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;SameSite=Lax` +} + +const cookieStorage = { + getItem: (key: string): string | null => { + console.log('Getting cookie', key) + return getCookie(key) + }, + setItem: (key: string, value: string): void => { + console.log('Setting cookie', key, value.substring(0, 50) + '...') + setCookie(key, value, 7) // Store for 7 days + }, + removeItem: (key: string): void => { + console.log('Removing cookie', key) + deleteCookie(key) + } +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + storage: cookieStorage + } +}) + +export async function logout(): Promise { + const { error } = await supabase.auth.signOut() + if (error) { + console.error('Error signing out:', error) + } + deleteCookie('sb-access-token') + deleteCookie('sb-refresh-token') + deleteCookie('sb-user') + window.location.href = '/' +} \ No newline at end of file diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index 1fbdff69..c7534c97 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -6,7 +6,19 @@ import Logo from "$lib/img/logos/logo_white.webp"; import LoginBg from "$lib/img/backgrounds/login.webp"; - import Microsoft from "$lib/img/logos/microsoft.svg"; + import { supabase } from "$lib/supabase"; + + async function signInWithGoogle() { + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo: `${window.location.origin}/dashboard` + } + }); + if (error) { + console.error('Error signing in with Google:', error); + } + }
@@ -16,10 +28,9 @@
TigerRacing logo - - Microsoft logo - Sign in with Microsoft - + Don't have an account? Apply now! From 410275cf833eace031432ed10389da76f815b894 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:47:03 -0600 Subject: [PATCH 05/13] reset --- frontend/src/lib/supabase.ts | 54 -------------------------- frontend/src/routes/login/+page.svelte | 23 +++-------- 2 files changed, 6 insertions(+), 71 deletions(-) delete mode 100644 frontend/src/lib/supabase.ts diff --git a/frontend/src/lib/supabase.ts b/frontend/src/lib/supabase.ts deleted file mode 100644 index 962bdca3..00000000 --- a/frontend/src/lib/supabase.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createClient } from '@supabase/supabase-js' - -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY - -function getCookie(name: string): string | null { - const value = `; ${document.cookie}` - const parts = value.split(`; ${name}=`) - if (parts.length === 2) return parts.pop()?.split(';').shift() || null - return null -} - -function setCookie(name: string, value: string, days: number = 7): void { - const expires = new Date() - expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000) - document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax` - console.log(`Cookie created with name: ${name}`) -} - -function deleteCookie(name: string): void { - document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;SameSite=Lax` -} - -const cookieStorage = { - getItem: (key: string): string | null => { - console.log('Getting cookie', key) - return getCookie(key) - }, - setItem: (key: string, value: string): void => { - console.log('Setting cookie', key, value.substring(0, 50) + '...') - setCookie(key, value, 7) // Store for 7 days - }, - removeItem: (key: string): void => { - console.log('Removing cookie', key) - deleteCookie(key) - } -} - -export const supabase = createClient(supabaseUrl, supabaseAnonKey, { - auth: { - storage: cookieStorage - } -}) - -export async function logout(): Promise { - const { error } = await supabase.auth.signOut() - if (error) { - console.error('Error signing out:', error) - } - deleteCookie('sb-access-token') - deleteCookie('sb-refresh-token') - deleteCookie('sb-user') - window.location.href = '/' -} \ No newline at end of file diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index c7534c97..a722811b 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -6,19 +6,7 @@ import Logo from "$lib/img/logos/logo_white.webp"; import LoginBg from "$lib/img/backgrounds/login.webp"; - import { supabase } from "$lib/supabase"; - - async function signInWithGoogle() { - const { error } = await supabase.auth.signInWithOAuth({ - provider: 'google', - options: { - redirectTo: `${window.location.origin}/dashboard` - } - }); - if (error) { - console.error('Error signing in with Google:', error); - } - } + import Microsoft from "$lib/img/logos/microsoft.svg";
@@ -28,9 +16,10 @@
TigerRacing logo - + + Microsoft logo + Sign in with Microsoft + Don't have an account? Apply now! @@ -57,4 +46,4 @@ a:hover > div { background-color: var(--secondary); } - + \ No newline at end of file From 6ab7856d484cb04c8486f8d0ff96119ad71eb2be Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:50:29 -0600 Subject: [PATCH 06/13] reset --- frontend/src/routes/login/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index a722811b..7910c892 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -46,4 +46,4 @@ a:hover > div { background-color: var(--secondary); } - \ No newline at end of file + From fa128f6fc8571dccae805b7a56196502de1f4fe6 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:34:55 -0600 Subject: [PATCH 07/13] feat: auth with asp.net --- .../Controllers/AuthController.cs | 123 +++++++++++++++--- backend/TRFSAE.MemberPortal.API/Enums.cs | 3 +- backend/TRFSAE.MemberPortal.API/Program.cs | 5 +- .../Services/AuthService.cs | 1 - 4 files changed, 109 insertions(+), 23 deletions(-) diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs index 91eef8e2..1c24b81b 100644 --- a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -1,58 +1,143 @@ using Microsoft.AspNetCore.Mvc; +using Supabase; using TRFSAE.MemberPortal.API.Interfaces; using TRFSAE.MemberPortal.API.Models; namespace TRFSAE.MemberPortal.API.Controllers; [ApiController] -[Route("api/auth")] +[Route("api/auth")] // Simplified route public class AuthController : ControllerBase { private readonly IAuthService _authService; + private readonly Client _supabaseClient; // Inject the Supabase client here - public AuthController(IAuthService authService) + public AuthController(IAuthService authService, Client supabaseClient) { _authService = authService; + _supabaseClient = supabaseClient; } - [HttpPost("callback")] - public async Task Callback() + [HttpGet("google")] + public async Task GoogleLogin() { - var token = GetTokenFromHeader(); - if (string.IsNullOrEmpty(token)) + var callbackUrl = "http://localhost:5096/api/auth/callback"; + var verifierBytes = new byte[32]; + System.Security.Cryptography.RandomNumberGenerator.Fill(verifierBytes); + var pkceVerifier = Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(verifierBytes); + + var challengeBytes = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(pkceVerifier)); + var pkceChallenge = Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(challengeBytes); + + var options = new Supabase.Gotrue.SignInOptions { - return Unauthorized(); + RedirectTo = callbackUrl, + QueryParams = new Dictionary + { + { "code_challenge", pkceChallenge }, + { "code_challenge_method", "s256" } + } + }; + + var providerState = await _supabaseClient.Auth.SignIn(Supabase.Gotrue.Constants.Provider.Google, options); + + Response.Cookies.Append("pkce_verifier", pkceVerifier, new CookieOptions + { + HttpOnly = true, + Secure = Request.IsHttps, // Set to true in production + SameSite = SameSiteMode.Lax, + Expires = DateTimeOffset.UtcNow.AddMinutes(5) + }); + + return Redirect(providerState.Uri.ToString()); + } + + [HttpGet("callback")] + public async Task Callback([FromQuery] string code) + { + if (string.IsNullOrEmpty(code)) return BadRequest("Missing authorization code."); + + if (!Request.Cookies.TryGetValue("pkce_verifier", out var pkceVerifier)) + { + return BadRequest("PKCE verifier missing or expired. Please try logging in again."); } - await _authService.SyncUserToDatabase(token); - return Ok(new { message = "User synchronized successfully" }); + try + { + var session = await _supabaseClient.Auth.ExchangeCodeForSession(pkceVerifier, code); + + Response.Cookies.Append("access_token", session.AccessToken, new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Lax, + Expires = DateTimeOffset.UtcNow.AddSeconds(session.ExpiresIn) + }); + + Response.Cookies.Delete("pkce_verifier"); + + await _authService.SyncUserToDatabase(session.AccessToken); + + return Redirect("http://localhost:3000/dashboard"); + } + catch (Exception ex) + { + return StatusCode(500, "Authentication failed: " + ex.Message); + } } [HttpGet("me")] public async Task Me() { - var token = GetTokenFromHeader(); - if (string.IsNullOrEmpty(token) || !_authService.ValidateSupabaseToken(token)) + if (!Request.Cookies.TryGetValue("access_token", out var token)) { return Unauthorized(); } + if (!_authService.ValidateSupabaseToken(token)) return Unauthorized(); + var user = await _authService.GetUserFromToken(token); - if (user == null) + if (user == null) return NotFound(new { message = "User not found" }); + + return Ok(user); + } + + [HttpPost("logout")] + public async Task Logout() + { + try { - return NotFound(new { message = "User not found" }); + await _supabaseClient.Auth.SignOut(); + } + catch + { + //ensures that cookie is deleted even when waiting for supabase } - return Ok(user); + Response.Cookies.Delete("access_token"); + return Ok(new { message = "Successfully logged out." }); } - private string GetTokenFromHeader() + [HttpGet("test-auth")] + public IActionResult TestAuth() { - var authHeader = HttpContext.Request.Headers["Authorization"].ToString(); - if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) + // A simple endpoint to visually verify the cookie is working + // without needing the Svelte frontend running. + if (Request.Cookies.TryGetValue("access_token", out var token)) { - return null; + return Ok(new + { + status = "Authenticated ✅", + message = "The HttpOnly cookie was successfully set and attached to this request!", + // Print just the start of the token to prove it's there without exposing the whole thing + tokenPrefix = token.Substring(0, 15) + "..." + }); } - return authHeader.Substring("Bearer ".Length); + + return Unauthorized(new + { + status = "Unauthenticated ❌", + message = "No access_token cookie found. You are not logged in." + }); } } \ No newline at end of file diff --git a/backend/TRFSAE.MemberPortal.API/Enums.cs b/backend/TRFSAE.MemberPortal.API/Enums.cs index d460a121..fed6b503 100644 --- a/backend/TRFSAE.MemberPortal.API/Enums.cs +++ b/backend/TRFSAE.MemberPortal.API/Enums.cs @@ -57,7 +57,8 @@ public enum Role Admin, SystemLead, SubsystemLead, - Member + Member, + Unverified } [JsonConverter(typeof(StringEnumConverter))] diff --git a/backend/TRFSAE.MemberPortal.API/Program.cs b/backend/TRFSAE.MemberPortal.API/Program.cs index 7e5e0c42..11331a75 100644 --- a/backend/TRFSAE.MemberPortal.API/Program.cs +++ b/backend/TRFSAE.MemberPortal.API/Program.cs @@ -13,6 +13,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); } // register Supabase client as scoped for reuse across project @@ -71,7 +72,7 @@ // CORS stuff builder.Services.AddCors(options => { - options.AddPolicy("AllowReactApp", policy => + options.AddPolicy("AllowSvelteApp", policy => { policy.WithOrigins("http://localhost:3000") .AllowAnyHeader() @@ -82,7 +83,7 @@ var app = builder.Build(); -app.UseCors("AllowReactApp"); +app.UseCors("AllowSvelteApp"); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) diff --git a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs index da707afc..fcf3dc5a 100644 --- a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs +++ b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs @@ -27,7 +27,6 @@ public bool ValidateSupabaseToken(string token) { var handler = new JwtSecurityTokenHandler(); var jwtToken = handler.ReadJwtToken(token); - // Basic validation: check if token is not expired return jwtToken.ValidTo > DateTime.UtcNow; } catch From eee42b6d51a4109879f79a15d7b7131dc1e367f3 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:58:56 -0600 Subject: [PATCH 08/13] feat: auth with asp.net --- backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs index 1c24b81b..12155a27 100644 --- a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -78,7 +78,7 @@ public async Task Callback([FromQuery] string code) await _authService.SyncUserToDatabase(session.AccessToken); - return Redirect("http://localhost:3000/dashboard"); + return Redirect("http://localhost:3000"); } catch (Exception ex) { @@ -118,6 +118,7 @@ public async Task Logout() return Ok(new { message = "Successfully logged out." }); } +// AI-Given test url- delete later [HttpGet("test-auth")] public IActionResult TestAuth() { From a2b747350fabe536db1e42d9fd4c0c67e697d9ae Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:30:12 -0500 Subject: [PATCH 09/13] ready to pr --- backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs index 12155a27..52c5ba73 100644 --- a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -6,11 +6,11 @@ namespace TRFSAE.MemberPortal.API.Controllers; [ApiController] -[Route("api/auth")] // Simplified route +[Route("api/auth")] public class AuthController : ControllerBase { private readonly IAuthService _authService; - private readonly Client _supabaseClient; // Inject the Supabase client here + private readonly Client _supabaseClient; public AuthController(IAuthService authService, Client supabaseClient) { From fc77fea6e6c1aa4f674301b5fe2245a1ab8c2c08 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:48:32 -0500 Subject: [PATCH 10/13] ready to pr --- backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs | 4 ++-- backend/TRFSAE.MemberPortal.API/Services/AuthService.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs index 52c5ba73..1042d62f 100644 --- a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -21,7 +21,7 @@ public AuthController(IAuthService authService, Client supabaseClient) [HttpGet("google")] public async Task GoogleLogin() { - var callbackUrl = "http://localhost:5096/api/auth/callback"; + var callbackUrl = "http://127.0.0.1:5096/api/auth/callback"; //change to actual auth frontend url when deployed var verifierBytes = new byte[32]; System.Security.Cryptography.RandomNumberGenerator.Fill(verifierBytes); var pkceVerifier = Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(verifierBytes); @@ -78,7 +78,7 @@ public async Task Callback([FromQuery] string code) await _authService.SyncUserToDatabase(session.AccessToken); - return Redirect("http://localhost:3000"); + return Redirect("http://127.0.0.1:3000/"); } catch (Exception ex) { diff --git a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs index fcf3dc5a..b0b78306 100644 --- a/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs +++ b/backend/TRFSAE.MemberPortal.API/Services/AuthService.cs @@ -46,7 +46,7 @@ public bool ValidateSupabaseToken(string token) var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; if (userIdClaim == null) return null; - var userDto = await _userService.GetUserByIDAsync(Guid.Parse(userIdClaim)); + var userDto = await _userService.GetUserAsync(Guid.Parse(userIdClaim)); if (userDto == null) return null; return new UserModel From f9049b9ff86e615733bb31af2193e2a513b55f85 Mon Sep 17 00:00:00 2001 From: Blaze34536 <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:49:47 -0500 Subject: [PATCH 11/13] ready to pr --- frontend/src/routes/login/+page.svelte | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index ba8f3b55..578c961b 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -16,15 +16,9 @@
TigerRacing logo -<<<<<<< HEAD - - Microsoft logo - Sign in with Microsoft -======= - + Google logo Sign in with Google ->>>>>>> origin/main Don't have an account? From 94dc100cb343ade915c6c0cb2e86227ec51f5b84 Mon Sep 17 00:00:00 2001 From: Nihal Mohapatro <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:57:28 -0500 Subject: [PATCH 12/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs index 1042d62f..a9dcdfdb 100644 --- a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -69,7 +69,7 @@ public async Task Callback([FromQuery] string code) Response.Cookies.Append("access_token", session.AccessToken, new CookieOptions { HttpOnly = true, - Secure = true, + Secure = Request.IsHttps, SameSite = SameSiteMode.Lax, Expires = DateTimeOffset.UtcNow.AddSeconds(session.ExpiresIn) }); From afefa0bca87a0d909f539f470d167137f86c7b03 Mon Sep 17 00:00:00 2001 From: Nihal Mohapatro <88854346+Blaze34536@users.noreply.github.com> Date: Wed, 18 Mar 2026 22:01:08 -0500 Subject: [PATCH 13/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Controllers/AuthController.cs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs index a9dcdfdb..4314563c 100644 --- a/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs +++ b/backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs @@ -117,28 +117,4 @@ public async Task Logout() Response.Cookies.Delete("access_token"); return Ok(new { message = "Successfully logged out." }); } - -// AI-Given test url- delete later - [HttpGet("test-auth")] - public IActionResult TestAuth() - { - // A simple endpoint to visually verify the cookie is working - // without needing the Svelte frontend running. - if (Request.Cookies.TryGetValue("access_token", out var token)) - { - return Ok(new - { - status = "Authenticated ✅", - message = "The HttpOnly cookie was successfully set and attached to this request!", - // Print just the start of the token to prove it's there without exposing the whole thing - tokenPrefix = token.Substring(0, 15) + "..." - }); - } - - return Unauthorized(new - { - status = "Unauthenticated ❌", - message = "No access_token cookie found. You are not logged in." - }); - } } \ No newline at end of file