PROBLEM:
- Login returns JWT token with 'sub' claim
- /me endpoint tries to read user ID from JWT
- Gets 401 Unauthorized because user ID claim cannot be found
ROOT CAUSE:
ASP.NET Core JWT middleware automatically maps JWT standard claims to .NET claim types:
- JwtRegisteredClaimNames.Sub ('sub') -> ClaimTypes.NameIdentifier ('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier')
Controllers were looking for 'sub' or 'nameid' but JWT middleware creates the claim with the full URI.
SOLUTION:
Updated all controllers to use ClaimTypes.NameIdentifier with fallback to 'sub':
- AuthController.GetCurrentUser()
- AdminController.DeleteUserAsync()
- StoryController.GetUserId()
This ensures the user ID can be found regardless of how the JWT middleware maps the claims.
CHANGES:
- AuthService: Generates JWT tokens with JwtRegisteredClaimNames.Sub (JWT standard)
- AuthController: Uses ClaimTypes.NameIdentifier ?? 'sub' fallback
- AdminController: Uses ClaimTypes.NameIdentifier ?? 'sub' fallback
- StoryController: Uses ClaimTypes.NameIdentifier ?? 'sub' fallback
- LessonsEndpoints.cs: Added .RequireAuthorization() to all GET endpoints
- docs/features/admin-module.md: Updated acceptance criteria and requirements
- Added unit tests in JwtTokenValidationTests.cs to verify the fix
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Changed AuthService.GenerateJwtToken to use JWT standard claims:
- JwtRegisteredClaimNames.Sub for user ID (instead of ClaimTypes.NameIdentifier)
- JwtRegisteredClaimNames.Name for username
- JwtRegisteredClaimNames.Email for email
- JwtRegisteredClaimNames.UniqueName for additional username claim
- Kept ClaimTypes.Role for role
- Updated AuthController.GetCurrentUser to use JwtRegisteredClaimNames.Sub
- Updated AdminController.DeleteUserAsync to use JwtRegisteredClaimNames.Sub
This fixes the 401 error when calling /me after login. The issue was that
tokens were being generated with ClaimTypes.NameIdentifier claim, but the
JWT middleware doesn't automatically map this to a claim that can be found
with User.FindFirst(). Using standard JWT claims (sub, name, email) ensures
proper compatibility with ASP.NET Core's JWT authentication.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Fix case-insensitive email lookups in AuthService (RegisterAsync, LoginAsync, CreateAdminUserAsync)
- Fix User.Create factory method to explicitly set Role property (C# object initializer behavior)
- Assign Admin role to seeded admin user in SeedDataExtension
- Add [AllowAnonymous] to Register and Login endpoints in AuthController (defensive programming)
- Increase JWT ClockSkew to 5 minutes for clock difference tolerance
These fixes resolve issues where:
- Login fails with 401 due to case-sensitive email comparison
- User role is null instead of 'User' or 'Admin'
- JWT token validation too strict on clock synchronization
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add Role property to User entity with migration
- Create BootstrapController for first admin user creation
- Remove [AllowAnonymous] from all learning content controllers
- Create AdminController with admin-only endpoints
- Create AdminService for user management
- Create UserReportService for progress reports
- Add UserRepository implementation
- Update AuthService with role support
feat(frontend): Implement authentication system
- Add AuthStore with React context for auth state management
- Create Login, Register, Landing, and Home pages
- Add ProtectedRoute and AdminRoute components
- Create Auth API types and client
- Configure Vite with @/ path alias
- Add comprehensive CSS styles for auth and landing pages
BREAKING CHANGE: All learning content now requires authentication.
Users must register and sign in before accessing lessons, quizzes, and stories.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Created admin-module.md with comprehensive feature documentation
- Updated README.md roadmap with current status and progress
- Added [Authorize] to StoryController GET endpoints
- Documented requirements:
- Mandatory user registration before accessing content
- Individual user progress tracking
- Admin module (Lasse only)
- Admin story generation functionality
- Admin user progress reports
- Updated development roadmap with Phase 4 (Admin Module)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- GetSegmentsByLevelAsync now returns Ok([]) instead of NotFound() when no segments exist
- Prevents frontend from throwing errors when database has no story segments
- More RESTful: 200 with empty array vs 404 for non-existence
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Removed class-level [Authorize] from StoryController
- Admin endpoints already have [Authorize(Roles = "Admin")]
- GET endpoints are now publicly accessible for development/testing
- Updated frontend API client to handle unauthenticated requests
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Implement story integration backend services:
- Create StoryService (Application/Services/StoryService.cs) with full CRUD operations
- Create StoryGenerationService (Application/Services/StoryGenerationService.cs) for AI-powered story generation
- Uses IMistralService for text generation
- Uses ITtsService for audio generation
- Splits stories into segments per lesson
- Create StoryUnlockService (Application/Services/StoryUnlockService.cs) for progress management
- Handles lesson completion → story segment unlocking
- Create StoryController (Presentation/Controllers/StoryController.cs) with 12 endpoints:
- GET /api/story/levels - list levels with stories
- GET /api/story/levels/{levelId}/segments - get segments for level
- GET /api/story/segments/{id} - get specific segment
- POST /api/story/segments - create segment (Admin)
- PUT /api/story/segments/{id} - update segment (Admin)
- DELETE /api/story/segments/{id} - delete segment (Admin)
- POST /api/story/levels/{levelId}/generate - generate story with AI (Admin)
- POST /api/story/segments/{segmentId}/audio - generate audio (Admin)
- GET /api/story/levels/{levelId}/progress - get user progress
- POST /api/story/segments/{segmentId}/complete - mark as completed
- GET /api/story/levels/{levelId}/next - get next segment
- GET /api/story/segments/{segmentId}/unlocked - check if unlocked
- GET /api/story/segments/{segmentId}/audio - get audio URL
- GET /api/story/levels/{levelId}/lessons-with-stories - get lessons with story status
- Register all services in Program.cs DI container
- Update feature document to reflect Phase 2 completion
Next: Phase 3 - AI Integration (unit tests for services)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add QuizQuestion and QuizOption domain entities with QuestionType enum
- Add IQuizQuestionRepository and IQuizOptionRepository interfaces
- Add QuizQuestionRepository and QuizOptionRepository EF Core implementations
- Add QuizQuestionService with CRUD, quiz submission, and statistics
- Add QuizQuestionsController with comprehensive REST API endpoints
- Add QuizQuestionDto and related DTOs for API communication
- Add EF Core migration (20260612050652_AddQuizQuestionTables) for QuizQuestion and QuizOption
- Add seed data with sample quiz questions for Greetings, Numbers, Grammar lessons
- Update AppDbContext with DbSets and entity configurations
- Update Program.cs with migration retry logic for Docker
- Update docker-compose.yml with SeedDatabase configuration
- Add unit tests for QuizQuestionService
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Created LevelsController.cs with full CRUD operations
- GET /api/levels - all levels
- GET /api/levels/{id} - level by ID
- GET /api/levels/by-code/{code} - level by code
- GET /api/levels/first - first level
- GET /api/levels/next/{id} - next level
- POST /api/levels - create level (Admin)
- PUT /api/levels/{id} - update level (Admin)
- DELETE /api/levels/{id} - delete level (Admin)
- Created LessonsController.cs with full CRUD and filtering
- GET /api/lessons - all lessons
- GET /api/lessons/ordered - ordered lessons
- GET /api/lessons/{id} - lesson by ID
- GET /api/lessons/by-level/{levelId} - lessons by level
- GET /api/lessons/beginner - beginner lessons
- GET /api/lessons/advanced - advanced lessons
- GET /api/lessons/first/{levelId} - first lesson in level
- GET /api/lessons/next/{id} - next lesson
- GET /api/lessons/accessible/{userId} - accessible lessons
- GET /api/lessons/unlocked/{userId}/{lessonId} - check unlock
- POST /api/lessons - create lesson (Admin)
- PUT /api/lessons/{id} - update lesson (Admin)
- DELETE /api/lessons/{id} - delete lesson (Admin)
- Added authorization: [Authorize] for most endpoints, [AllowAnonymous] for read-only
- Added Admin role requirement for write operations
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Fix MSTest compatibility with .NET 9.0 by upgrading to MSTest.TestFramework 4.2.3
- Move Tests directory to solution level (Tests/) to prevent test files from being compiled with GermanApp
- Update test project references to point to GermanApp/GermanApp.csproj
- Add InternalsVisibleTo attributes for test assemblies
- Make RefreshToken properties internal for testability
- Fix User.ChangeEmail null handling
- Add 46 comprehensive unit tests for User and RefreshToken domain entities
- All tests passing successfully
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>