diff --git a/docs/diagrams-out/architecture.svg b/docs/diagrams-out/architecture.svg new file mode 100644 index 0000000..f7b8cdd --- /dev/null +++ b/docs/diagrams-out/architecture.svg @@ -0,0 +1 @@ +The Biergarten App - Layered ArchitectureThe Biergarten App - Layered ArchitectureAPI LayerService LayerInfrastructure LayerDomain LayerSQL ServerAPI.CoreASP.NET Core Web API- Controllers (Auth, User)- Swagger/OpenAPI- Middleware- Health ChecksService.AuthService.UserManagement- Business Logic- Validation- OrchestrationInfrastructure.RepositoryInfrastructure.JwtInfrastructure.PasswordHashingInfrastructure.EmailDomain.Entities- UserAccount- UserCredential- UserVerificationStored ProceduresTablesSQL-first approachAll queries viastored procedures \ No newline at end of file diff --git a/docs/diagrams-out/authentication-flow.svg b/docs/diagrams-out/authentication-flow.svg new file mode 100644 index 0000000..178d020 --- /dev/null +++ b/docs/diagrams-out/authentication-flow.svg @@ -0,0 +1 @@ +User Authentication Flow - ExpandedUser Authentication Flow - ExpandedService LayerInfrastructure LayerRepository LayerAPIAPIRegisterServiceLoginServiceTokenServiceTokenServiceTokenServiceTokenServiceEmailServiceArgon2Argon2JWTJWTJWTJWTEmailTemplateAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositorySQL ServerSQL ServerSQL ServerSQL ServerSQL ServerUserAPIRegisterServiceLoginServiceTokenServiceEmailServiceArgon2JWTEmailTemplateAuthRepositoryUserAccountSQL ServerUserUserAPIControllerAPIControllerRegisterServiceRegisterServiceLoginServiceLoginServiceTokenServiceTokenServiceEmailServiceEmailServiceArgon2InfrastructureArgon2InfrastructureJWTInfrastructureJWTInfrastructureEmailProviderEmailProviderTemplateProviderTemplateProviderAuthRepositoryAuthRepositoryUserAccountRepositoryUserAccountRepositorySQL ServerStored ProceduresSQL ServerStored ProceduresAPIAPIRegisterServiceLoginServiceTokenServiceTokenServiceTokenServiceTokenServiceEmailServiceArgon2Argon2JWTJWTJWTJWTEmailTemplateAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositorySQL ServerSQL ServerSQL ServerSQL ServerSQL ServerRegistration FlowPOST /api/auth/register{username, firstName, lastName,email, dateOfBirth, password}FluentValidation runs:- Username: 3-64 chars, alphanumeric + [._-]- Email: valid format, max 128 chars- Password: min 8 chars, uppercase,\n lowercase, number, special char- DateOfBirth: must be 19+ years oldValidate request(FluentValidation)alt[Validation fails]400 Bad Request{errors: {...}}[Validation succeeds]RegisterAsync(userAccount,password)GetUserByUsernameAsync(username)EXECusp_GetUserAccountByUsernamenull (user doesn't exist)GetUserByEmailAsync(email)EXECusp_GetUserAccountByEmailnull (email doesn't exist)alt[User/Email already exists]throw ConflictException409 Conflict"Username or email alreadyexists"[User doesn't exist]Hash(password)Argon2id parameters:- Salt: 16 bytes (128-bit)- Memory: 64MB- Iterations: 4- Parallelism: CPU count- Hash output: 32 bytesGenerate random salt(16 bytes)Hash password withArgon2id algorithm"base64(salt):base64(hash)"RegisterUserAsync(  username, firstName, lastName,  email, dateOfBirth, hash)EXEC USP_RegisterUserTransaction begins:1. INSERT UserAccount2. INSERT UserCredential(with hashed password)Transaction commitsBEGIN TRANSACTIONINSERT INTO UserAccount(Username, FirstName,LastName,Email, DateOfBirth)OUTPUTINSERTED.UserAccountIDINSERT INTO UserCredential(UserAccountId, Hash)COMMIT TRANSACTIONUserAccountId (GUID)UserAccount entityGenerateAccessToken(userAccount)GenerateJwt(userId, username,expiry)JWT Configuration:- Algorithm: HS256- Expires: 1 hour- Claims:* sub: userId* unique_name: username* jti: unique token IDCreate JWT with claimsSign with secret keyAccess TokenAccess TokenGenerateRefreshToken(userAccount)GenerateJwt(userId, username,expiry)Refresh Token:- Expires: 21 days- Same structure as access tokenRefresh TokenRefresh TokenSendRegistrationEmailAsync(  createdUser, confirmationToken)RenderUserRegisteredEmailAsync(  firstName, confirmationLink)Razor Component:- Header with branding- Welcome message- Confirmation button- FooterRender Razor componentto HTMLHTML email contentSendAsync(email, subject, body)SMTP Configuration:- Host: from env (SMTP_HOST)- Port: from env (SMTP_PORT)- TLS: StartTLS- Auth: username/passwordCreate MIME messageConnect to SMTP serverAuthenticateSend emailDisconnectSuccess / Failurealt[Email sent successfully]emailSent = true[Email failed]emailSent = false(error suppressed)RegisterServiceReturn(  userAccount, accessToken,  refreshToken, emailSent)Create response body201 Created{  message: "User registeredsuccessfully",  payload: {    userAccountId, username,    accessToken, refreshToken,    confirmationEmailSent  }}Login FlowPOST /api/auth/login{username, password}Validate request(FluentValidation)alt[Validation fails]400 Bad Request{errors: {...}}[Validation succeeds]LoginAsync(username, password)GetUserByUsernameAsync(username)EXECusp_GetUserAccountByUsernameSELECT FROM UserAccountWHERE Username = @UsernameUserAccount entityalt[User not found]throw UnauthorizedException"Invalid username or password"401 Unauthorized[User found]GetActiveCredentialByUserAccountIdAsync(userId)EXECUSP_GetActiveUserCredentialByUserAccountIdSELECT FROM UserCredentialWHERE UserAccountId = @UserAccountIdAND IsRevoked = 0UserCredential entityalt[No active credential]throw UnauthorizedException401 Unauthorized[Active credential found]Verify(password, storedHash)1. Split stored hash: "salt:hash"2. Extract salt3. Hash provided password\n with same salt4. Constant-time comparisonParse salt from stored hashHash provided passwordwith extracted saltFixedTimeEquals(  computed, stored)true/falsealt[Password invalid]throw UnauthorizedException401 Unauthorized[Password valid]GenerateAccessToken(user)GenerateJwt(...)Access TokenAccess TokenGenerateRefreshToken(user)GenerateJwt(...)Refresh TokenRefresh TokenLoginServiceReturn(  userAccount, accessToken,  refreshToken)200 OK{  message: "Logged insuccessfully",  payload: {    userAccountId, username,    accessToken, refreshToken  }}Error Handling (Global Exception Filter)GlobalExceptionFilter catches:- ValidationException → 400 Bad Request- ConflictException → 409 Conflict- NotFoundException → 404 Not Found- UnauthorizedException → 401 Unauthorized- ForbiddenException → 403 Forbidden- All others → 500 Internal Server Error \ No newline at end of file diff --git a/docs/diagrams-out/database-schema.svg b/docs/diagrams-out/database-schema.svg new file mode 100644 index 0000000..47169b0 --- /dev/null +++ b/docs/diagrams-out/database-schema.svg @@ -0,0 +1 @@ +Key Database Schema - User & AuthenticationKey Database Schema - User & AuthenticationUserAccountUserAccountId: INT «PK»Username: NVARCHAR(30) «UNIQUE»Email: NVARCHAR(255) «UNIQUE»FirstName: NVARCHAR(50)LastName: NVARCHAR(50)Bio: NVARCHAR(500)CreatedAt: DATETIME2UpdatedAt: DATETIME2LastLoginAt: DATETIME2UserCredentialUserCredentialId: INT «PK»UserAccountId: INT «FK»PasswordHash: VARBINARY(32)PasswordSalt: VARBINARY(16)CredentialRotatedAt: DATETIME2CredentialExpiresAt: DATETIME2CredentialRevokedAt: DATETIME2IsActive: BITCreatedAt: DATETIME2UserVerificationUserVerificationId: INT «PK»UserAccountId: INT «FK»IsVerified: BITVerifiedAt: DATETIME2VerificationToken: NVARCHAR(255)TokenExpiresAt: DATETIME2UserAvatarUserAvatarId: INT «PK»UserAccountId: INT «FK»PhotoId: INT «FK»IsActive: BITCreatedAt: DATETIME2UserFollowUserFollowId: INT «PK»FollowerUserId: INT «FK»FollowedUserId: INT «FK»CreatedAt: DATETIME2PhotoPhotoId: INT «PK»Url: NVARCHAR(500)CloudinaryPublicId: NVARCHAR(255)Width: INTHeight: INTFormat: NVARCHAR(10)CreatedAt: DATETIME2Password hashing:- Algorithm: Argon2id- Memory: 64MB- Iterations: 4- Salt: 128-bit- Hash: 256-bitAccount verificationvia email tokenwith expiryCore stored procedures:- USP_RegisterUser- USP_GetUserAccountByUsername- USP_RotateUserCredential- USP_UpdateUserAccounthashashasfollowsfollowed byrefers to \ No newline at end of file diff --git a/docs/diagrams-out/deployment.svg b/docs/diagrams-out/deployment.svg new file mode 100644 index 0000000..4c28a85 --- /dev/null +++ b/docs/diagrams-out/deployment.svg @@ -0,0 +1 @@ +Docker Deployment ArchitectureDocker Deployment ArchitectureDevelopment Environment(docker-compose.dev.yaml)SQL Server(mcr.microsoft.com/mssql/server:2022-latest)BiergartenDatabaseAPI Container(API.Core)Migrations(run-once)Seed(run-once)Test Environment(docker-compose.test.yaml)SQL Server(isolated instance)BiergartenTest DatabaseMigrations(test)Seed(test)API.Specs(Integration Tests)Infrastructure.Repository.Tests(Unit Tests)Service.Auth.Tests(Unit Tests)test-results/(mounted volume)Environment:- ACCEPT_EULA=Y- SA_PASSWORD=***- MSSQL_PID=Developer Volumes:- biergarten-dev-data1433ASP.NET Core 108080:8080 (HTTP)8081:8081 (HTTPS)Environment:- ASPNETCORE_ENVIRONMENT=Development- DB_SERVER=sql-server- DB_NAME=Biergarten- DB_USER/PASSWORD- JWT_SECRET- SMTP_* (10+ variables) Health Check:/health endpointDatabase.MigrationsRuns: DbUp migrationsEnvironment:- CLEAR_DATABASE=falseDepends on: sql-serverDatabase.SeedCreates:- 100 test users- Location data (US/CA/MX)- test.user accountDepends on: migrationsFresh instance each runCLEAR_DATABASE=true Volumes:- biergarten-test-data(ephemeral)1434Database.MigrationsDatabase.SeedMinimal seed:- test.user only- Essential dataReqnroll + xUnitTests:- Registration flow- Login flow- Validation rules- 404 handling Uses: TestApiFactoryMocks: Email servicesxUnit + DbMockerTests:- AuthRepository- UserAccountRepository- SQL command building Uses: Mock connectionsNo real database neededxUnit + MoqTests:- RegisterService- LoginService- Token generation Uses: Mocked dependenciesNo database or infrastructureapi-specs/results.trxrepository-tests/results.trxservice-auth-tests/results.trxTRX formatReadable by:- Visual Studio- Azure DevOps- GitHub ActionsDeveloperDocker HostDev Network (bridge: biergarten-dev)Internal DNS:- sql-server (resolves to SQL container)- api (resolves to API container)Test Network (bridge: biergarten-test)All test components isolatedStartup Order:1. SQL Server (health check)2. Migrations (run-once)3. Seed (run-once)4. API (long-running)Test Execution:All tests run in parallelResults aggregatedProduction Deployment (not shown): Would include:• Azure SQL Database / AWS RDS• Azure Container Apps / ECS• Azure Key Vault for secrets• Application Insights / CloudWatch• Load balancer• HTTPS termination• CDN for static assetsdocker compose uphttp://localhost:80801. Run migrationsdepends_on2. Seed data3. Connect & servedepends_ondepends_on1. Migratedepends_on2. Seed3. Integration testMock (no connection)Mock (no connection)depends_ondepends_onExport TRXExport TRXExport TRX \ No newline at end of file diff --git a/docs/diagrams/architecture.puml b/docs/diagrams-src/architecture.puml similarity index 100% rename from docs/diagrams/architecture.puml rename to docs/diagrams-src/architecture.puml diff --git a/docs/diagrams/authentication-flow.puml b/docs/diagrams-src/authentication-flow.puml similarity index 100% rename from docs/diagrams/authentication-flow.puml rename to docs/diagrams-src/authentication-flow.puml diff --git a/docs/diagrams/database-schema.puml b/docs/diagrams-src/database-schema.puml similarity index 100% rename from docs/diagrams/database-schema.puml rename to docs/diagrams-src/database-schema.puml diff --git a/docs/diagrams/deployment.puml b/docs/diagrams-src/deployment.puml similarity index 100% rename from docs/diagrams/deployment.puml rename to docs/diagrams-src/deployment.puml diff --git a/docs/diagrams/class-diagram.puml b/docs/diagrams/class-diagram.puml deleted file mode 100644 index 6817eaf..0000000 --- a/docs/diagrams/class-diagram.puml +++ /dev/null @@ -1,523 +0,0 @@ -@startuml class-diagram -!theme plain -skinparam backgroundColor #FFFFFF -skinparam defaultFontName Arial -skinparam classAttributeIconSize 0 -skinparam linetype ortho - -title Biergarten Application - Class Diagram - -' API Layer -package "API.Core" <> #E3F2FD { - - class AuthController { - - IRegisterService _registerService - - ILoginService _loginService - + <> Task Register(RegisterRequest) - + <> Task Login(LoginRequest) - } - - class UserController { - - IUserService _userService - + <> Task>> GetAll(int?, int?) - + <> Task> GetById(Guid) - } - - class GlobalExceptionFilter { - - ILogger _logger - + void OnException(ExceptionContext) - } - - package "Contracts" { - class RegisterRequest <> { - + string Username - + string FirstName - + string LastName - + string Email - + DateTime DateOfBirth - + string Password - } - - class LoginRequest <> { - + string Username - + string Password - } - - class RegisterRequestValidator { - + RegisterRequestValidator() - } - - class LoginRequestValidator { - + LoginRequestValidator() - } - - class "ResponseBody" <> { - + string Message - + T Payload - } - - class LoginPayload <> { - + Guid UserAccountId - + string Username - + string RefreshToken - + string AccessToken - } - - class RegistrationPayload <> { - + Guid UserAccountId - + string Username - + string RefreshToken - + string AccessToken - + bool ConfirmationEmailSent - } - } -} - -' Service Layer -package "Service Layer" <> #C8E6C9 { - - package "Service.Auth" { - interface IRegisterService { - + <> Task RegisterAsync(UserAccount, string) - } - - class RegisterService { - - IAuthRepository _authRepo - - IPasswordInfrastructure _passwordInfra - - ITokenService _tokenService - - IEmailService _emailService - + <> Task RegisterAsync(UserAccount, string) - - <> Task ValidateUserDoesNotExist(UserAccount) - } - - interface ILoginService { - + <> Task LoginAsync(string, string) - } - - class LoginService { - - IAuthRepository _authRepo - - IPasswordInfrastructure _passwordInfra - - ITokenService _tokenService - + <> Task LoginAsync(string, string) - } - - interface ITokenService { - + string GenerateAccessToken(UserAccount) - + string GenerateRefreshToken(UserAccount) - } - - class TokenService { - - ITokenInfrastructure _tokenInfrastructure - + string GenerateAccessToken(UserAccount) - + string GenerateRefreshToken(UserAccount) - } - - class RegisterServiceReturn <> { - + bool IsAuthenticated - + bool EmailSent - + UserAccount UserAccount - + string AccessToken - + string RefreshToken - } - - class LoginServiceReturn <> { - + UserAccount UserAccount - + string RefreshToken - + string AccessToken - } - } - - package "Service.UserManagement" { - interface IUserService { - + <> Task> GetAllAsync(int?, int?) - + <> Task GetByIdAsync(Guid) - + <> Task UpdateAsync(UserAccount) - } - - class UserService { - - IUserAccountRepository _repository - + <> Task> GetAllAsync(int?, int?) - + <> Task GetByIdAsync(Guid) - + <> Task UpdateAsync(UserAccount) - } - } - - package "Service.Emails" { - interface IEmailService { - + <> Task SendRegistrationEmailAsync(UserAccount, string) - } - - class EmailService { - - IEmailProvider _emailProvider - - IEmailTemplateProvider _templateProvider - + <> Task SendRegistrationEmailAsync(UserAccount, string) - } - } -} - -' Domain Layer -package "Domain" <> #FFF9C4 { - - package "Domain.Entities" { - class UserAccount { - + Guid UserAccountId - + string Username - + string FirstName - + string LastName - + string Email - + DateTime CreatedAt - + DateTime? UpdatedAt - + DateTime DateOfBirth - + byte[]? Timer - } - - class UserCredential { - + Guid UserCredentialId - + Guid UserAccountId - + DateTime CreatedAt - + DateTime Expiry - + string Hash - + byte[]? Timer - } - - class UserVerification { - + Guid UserVerificationId - + Guid UserAccountId - + DateTime VerificationDateTime - + byte[]? Timer - } - } - - package "Domain.Exceptions" { - class ConflictException { - + ConflictException(string) - } - - class NotFoundException { - + NotFoundException(string) - } - - class UnauthorizedException { - + UnauthorizedException(string) - } - - class ForbiddenException { - + ForbiddenException(string) - } - - class ValidationException { - + ValidationException(string) - } - } -} - -' Infrastructure Layer -package "Infrastructure" <> #E1BEE7 { - - package "Infrastructure.Repository" { - interface IAuthRepository { - + <> Task RegisterUserAsync(string, string, string, string, DateTime, string) - + <> Task GetUserByEmailAsync(string) - + <> Task GetUserByUsernameAsync(string) - + <> Task GetActiveCredentialByUserAccountIdAsync(Guid) - + <> Task RotateCredentialAsync(Guid, string) - } - - class AuthRepository { - - ISqlConnectionFactory _connectionFactory - + <> Task RegisterUserAsync(...) - + <> Task GetUserByEmailAsync(string) - + <> Task GetUserByUsernameAsync(string) - + <> Task GetActiveCredentialByUserAccountIdAsync(Guid) - + <> Task RotateCredentialAsync(Guid, string) - # UserAccount MapToEntity(DbDataReader) - - UserCredential MapToCredentialEntity(DbDataReader) - } - - interface IUserAccountRepository { - + <> Task GetByIdAsync(Guid) - + <> Task> GetAllAsync(int?, int?) - + <> Task UpdateAsync(UserAccount) - + <> Task DeleteAsync(Guid) - + <> Task GetByUsernameAsync(string) - + <> Task GetByEmailAsync(string) - } - - class UserAccountRepository { - - ISqlConnectionFactory _connectionFactory - + <> Task GetByIdAsync(Guid) - + <> Task> GetAllAsync(int?, int?) - + <> Task UpdateAsync(UserAccount) - + <> Task DeleteAsync(Guid) - + <> Task GetByUsernameAsync(string) - + <> Task GetByEmailAsync(string) - # UserAccount MapToEntity(DbDataReader) - } - - abstract class "Repository" { - # ISqlConnectionFactory _connectionFactory - # <> Task CreateConnection() - # {abstract} T MapToEntity(DbDataReader) - } - - package "Sql" { - interface ISqlConnectionFactory { - + DbConnection CreateConnection() - } - - class DefaultSqlConnectionFactory { - - string _connectionString - + DbConnection CreateConnection() - } - - class SqlConnectionStringHelper <> { - + {static} string BuildConnectionString(string?) - + {static} string BuildMasterConnectionString() - } - } - } - - package "Infrastructure.PasswordHashing" { - interface IPasswordInfrastructure { - + string Hash(string) - + bool Verify(string, string) - } - - class Argon2Infrastructure { - - {static} int SaltSize = 16 - - {static} int HashSize = 32 - - {static} int ArgonIterations = 4 - - {static} int ArgonMemoryKb = 65536 - + string Hash(string) - + bool Verify(string, string) - } - } - - package "Infrastructure.Jwt" { - interface ITokenInfrastructure { - + string GenerateJwt(Guid, string, DateTime) - } - - class JwtInfrastructure { - - string? _secret - + string GenerateJwt(Guid, string, DateTime) - } - } - - package "Infrastructure.Email" { - interface IEmailProvider { - + <> Task SendAsync(string, string, string, bool) - + <> Task SendAsync(IEnumerable, string, string, bool) - } - - class SmtpEmailProvider { - - string _host - - int _port - - string? _username - - string? _password - - bool _useSsl - - string _fromEmail - - string _fromName - + <> Task SendAsync(string, string, string, bool) - + <> Task SendAsync(IEnumerable, string, string, bool) - } - } - - package "Infrastructure.Email.Templates" { - interface IEmailTemplateProvider { - + <> Task RenderUserRegisteredEmailAsync(string, string) - } - - class EmailTemplateProvider { - - IServiceProvider _serviceProvider - - ILoggerFactory _loggerFactory - + <> Task RenderUserRegisteredEmailAsync(string, string) - - <> Task RenderComponentAsync(Dictionary) - } - - class "UserRegistration <>" { - + string Username - + string ConfirmationLink - } - - class "Header <>" { - } - - class "Footer <>" { - + string? FooterText - } - } -} - -' Database Layer -package "Database" <> #FFCCBC { - - class "SQL Server" <> { - .. Tables .. - UserAccount - UserCredential - UserVerification - UserAvatar - Photo - UserFollow - Country - StateProvince - City - BreweryPost - BreweryPostLocation - BreweryPostPhoto - BeerStyle - BeerPost - BeerPostPhoto - BeerPostComment - .. Stored Procedures .. - USP_RegisterUser - usp_GetUserAccountByUsername - usp_GetUserAccountByEmail - usp_GetUserAccountById - USP_GetActiveUserCredentialByUserAccountId - USP_RotateUserCredential - USP_CreateUserVerification - } -} - -' Relationships - API to Service -AuthController ..> IRegisterService : uses -AuthController ..> ILoginService : uses -UserController ..> IUserService : uses - -AuthController ..> RegisterRequest : receives -AuthController ..> LoginRequest : receives -AuthController ..> "ResponseBody" : returns -AuthController ..> RegistrationPayload : returns -AuthController ..> LoginPayload : returns - -RegisterRequest ..> RegisterRequestValidator : validated by -LoginRequest ..> LoginRequestValidator : validated by - -' Relationships - Service Layer -IRegisterService <|.. RegisterService : implements -ILoginService <|.. LoginService : implements -ITokenService <|.. TokenService : implements -IUserService <|.. UserService : implements -IEmailService <|.. EmailService : implements - -RegisterService ..> IAuthRepository : uses -RegisterService ..> IPasswordInfrastructure : uses -RegisterService ..> ITokenService : uses -RegisterService ..> IEmailService : uses -RegisterService ..> RegisterServiceReturn : returns -RegisterService ..> UserAccount : uses - -LoginService ..> IAuthRepository : uses -LoginService ..> IPasswordInfrastructure : uses -LoginService ..> ITokenService : uses -LoginService ..> LoginServiceReturn : returns -LoginService ..> UserAccount : uses -LoginService ..> UserCredential : uses - -TokenService ..> ITokenInfrastructure : uses -TokenService ..> UserAccount : uses - -UserService ..> IUserAccountRepository : uses -UserService ..> UserAccount : uses - -EmailService ..> IEmailProvider : uses -EmailService ..> IEmailTemplateProvider : uses -EmailService ..> UserAccount : uses - -' Relationships - Repository Layer -IAuthRepository <|.. AuthRepository : implements -IUserAccountRepository <|.. UserAccountRepository : implements -"Repository" <|-- AuthRepository : extends -"Repository" <|-- UserAccountRepository : extends - -AuthRepository ..> ISqlConnectionFactory : uses -AuthRepository ..> UserAccount : returns -AuthRepository ..> UserCredential : returns -AuthRepository ..> "SQL Server" : queries - -UserAccountRepository ..> ISqlConnectionFactory : uses -UserAccountRepository ..> UserAccount : returns -UserAccountRepository ..> "SQL Server" : queries - -"Repository" ..> ISqlConnectionFactory : uses - -ISqlConnectionFactory <|.. DefaultSqlConnectionFactory : implements -DefaultSqlConnectionFactory ..> SqlConnectionStringHelper : uses - -' Relationships - Infrastructure -IPasswordInfrastructure <|.. Argon2Infrastructure : implements -ITokenInfrastructure <|.. JwtInfrastructure : implements -IEmailProvider <|.. SmtpEmailProvider : implements -IEmailTemplateProvider <|.. EmailTemplateProvider : implements - -EmailTemplateProvider ..> "UserRegistration <>" : renders -"UserRegistration <>" ..> "Header <>" : includes -"UserRegistration <>" ..> "Footer <>" : includes - -' Relationships - Domain -UserAccount -- UserCredential : "1" -- "*" -UserAccount -- UserVerification : "1" -- "0..1" - -' Exception handling -GlobalExceptionFilter ..> ConflictException : catches -GlobalExceptionFilter ..> NotFoundException : catches -GlobalExceptionFilter ..> UnauthorizedException : catches -GlobalExceptionFilter ..> ForbiddenException : catches -GlobalExceptionFilter ..> ValidationException : catches - -RegisterService ..> ConflictException : throws -LoginService ..> UnauthorizedException : throws -UserService ..> NotFoundException : throws - -' Notes -note right of Argon2Infrastructure - Security Parameters: - - Memory: 64MB - - Iterations: 4 - - Salt: 16 bytes - - Output: 32 bytes -end note - -note right of JwtInfrastructure - JWT Configuration: - - Algorithm: HS256 - - Access: 1 hour - - Refresh: 21 days -end note - -note right of "SQL Server" - Stored Procedures: - - USP_RegisterUser: Transaction - creates UserAccount + - UserCredential - - Credentials tracked with - IsRevoked flag -end note - -note right of AuthRepository - Uses ADO.NET with - parameterized queries - to prevent SQL injection -end note - -note bottom of RegisterService - Registration Flow: - 1. Validate user doesn't exist - 2. Hash password (Argon2) - 3. Create account + credential - 4. Generate tokens - 5. Send confirmation email -end note - -note bottom of LoginService - Login Flow: - 1. Find user by username - 2. Get active credential - 3. Verify password - 4. Generate tokens - 5. Return authenticated user -end note - -@enduml diff --git a/docs/getting-started.md b/docs/getting-started.md index 2333844..8074b94 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -185,36 +185,6 @@ npm run dev The frontend will be available at http://localhost:3000. -## Generate Diagrams (Optional) - -The project includes PlantUML diagrams that can be converted to PDF or PNG: - -### Install Java - -Make sure Java 8+ is installed: - -```bash -# Check Java version -java -version -``` - -### Generate Diagrams - -```bash -# Generate all PDFs -make - -# Generate PNGs -make pngs - -# Generate both -make diagrams - -# View help -make help -``` - -Generated diagrams will be in `docs/diagrams/pdf/`. ## Next Steps @@ -223,39 +193,3 @@ Generated diagrams will be in `docs/diagrams/pdf/`. - **Learn the Architecture**: Read [Architecture Overview](architecture.md) - **Understand Docker Setup**: See [Docker Guide](docker.md) - **Database Details**: Check [Database Schema](database.md) - -## Troubleshooting - -### Port Already in Use - -If port 8080 or 1433 is already in use, you can either: - -- Stop the service using that port -- Change the port mapping in `docker-compose.dev.yaml` - -### Database Connection Issues - -Check that: - -- SQL Server container is running: `docker ps` -- Connection string is correct in `.env.dev` -- Health check is passing: `docker compose -f docker-compose.dev.yaml ps` - -### Container Won't Start - -View container logs: - -```bash -docker compose -f docker-compose.dev.yaml logs -``` - -### Fresh Start - -Remove all containers and volumes: - -```bash -docker compose -f docker-compose.dev.yaml down -v -docker system prune -f -``` - -For more troubleshooting, see the [Docker Guide](docker.md#troubleshooting).