go-gin-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,937 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs").promises;
4
+ const path = require("path");
5
+ const createDir = require("../shares/createDir");
6
+ const createFile = require("../shares/createFile");
7
+ const { COLORS } = require("../constants/index");
8
+
9
+ /**
10
+ * AuthGenerator - Generates JWT and Passport authentication files for Go Clean Architecture
11
+ *
12
+ * Structure:
13
+ * src/
14
+ * ├── domain/
15
+ * │ └── models/auth.go
16
+ * ├── infrastructure/
17
+ * │ ├── common/
18
+ * │ │ ├── auth/
19
+ * │ │ │ ├── jwt.go
20
+ * │ │ │ ├── passport.go
21
+ * │ │ │ └── middleware.go
22
+ * │ │ └── guards/
23
+ * │ │ └── auth_guard.go
24
+ * │ └── repositories/auth/
25
+ * │ └── auth.repository.go
26
+ * └── usecases/auth/
27
+ * └── auth.usecase.go
28
+ */
29
+ class AuthGenerator {
30
+ constructor() {
31
+ this.srcDir = "src";
32
+ this.domainDir = path.join(this.srcDir, "domain");
33
+ this.infrastructureDir = path.join(this.srcDir, "infrastructure");
34
+ this.usecasesDir = path.join(this.srcDir, "usecases");
35
+
36
+ // Get go module name from go.mod
37
+ this.goModule = this.getGoModuleName();
38
+ }
39
+
40
+ getGoModuleName() {
41
+ try {
42
+ const goModPath = path.join(process.cwd(), "go.mod");
43
+ const content = require("fs").readFileSync(goModPath, "utf8");
44
+ const match = content.match(/module\s+(.+)/);
45
+ return match ? match[1].trim() : "github.com/example/project";
46
+ } catch {
47
+ return "github.com/example/project";
48
+ }
49
+ }
50
+
51
+ async execute() {
52
+ try {
53
+ console.log(`${COLORS.YELLOW}Starting authentication generation...${COLORS.NC}`);
54
+
55
+ // Check if auth already exists
56
+ const authDir = path.join(this.infrastructureDir, "common", "auth");
57
+ try {
58
+ await fs.access(authDir);
59
+ console.log(`${COLORS.YELLOW}⚠ Authentication module already exists${COLORS.NC}`);
60
+ return;
61
+ } catch {
62
+ // Directory doesn't exist, continue
63
+ }
64
+
65
+ await this.setupDirectories();
66
+ await this.generateDomainFiles();
67
+ await this.generateInfrastructureFiles();
68
+ await this.generateUsecaseFiles();
69
+ await this.updateGoMod();
70
+
71
+ console.log(`${COLORS.GREEN}Authentication generation completed successfully!${COLORS.NC}`);
72
+ this.printSummary();
73
+ } catch (error) {
74
+ console.error(`${COLORS.RED}Error during generation:${COLORS.NC} ${error.message}`);
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ async setupDirectories() {
80
+ const dirs = [
81
+ path.join(this.infrastructureDir, "common", "auth"),
82
+ path.join(this.infrastructureDir, "repositories", "auth"),
83
+ path.join(this.usecasesDir, "auth"),
84
+ ];
85
+
86
+ for (const dir of dirs) {
87
+ await createDir(dir);
88
+ }
89
+ console.log(`${COLORS.GREEN}✔ Created directories${COLORS.NC}`);
90
+ }
91
+
92
+ async generateDomainFiles() {
93
+ // Auth model
94
+ await createFile(
95
+ path.join(this.domainDir, "models", "auth.go"),
96
+ this.getAuthModelContent()
97
+ );
98
+ console.log(`${COLORS.GREEN}✔ Created domain/models/auth.go${COLORS.NC}`);
99
+ }
100
+
101
+ async generateInfrastructureFiles() {
102
+ // JWT utility
103
+ await createFile(
104
+ path.join(this.infrastructureDir, "common", "auth", "jwt.go"),
105
+ this.getJwtContent()
106
+ );
107
+ console.log(`${COLORS.GREEN}✔ Created infrastructure/common/auth/jwt.go${COLORS.NC}`);
108
+
109
+ // Passport (strategies)
110
+ await createFile(
111
+ path.join(this.infrastructureDir, "common", "auth", "passport.go"),
112
+ this.getPassportContent()
113
+ );
114
+ console.log(`${COLORS.GREEN}✔ Created infrastructure/common/auth/passport.go${COLORS.NC}`);
115
+
116
+ // Auth middleware
117
+ await createFile(
118
+ path.join(this.infrastructureDir, "common", "auth", "middleware.go"),
119
+ this.getMiddlewareContent()
120
+ );
121
+ console.log(`${COLORS.GREEN}✔ Created infrastructure/common/auth/middleware.go${COLORS.NC}`);
122
+
123
+ // Auth middleware for Gin
124
+ await createFile(
125
+ path.join(this.infrastructureDir, "common", "middleware", "auth.go"),
126
+ this.getAuthMiddlewareGinContent()
127
+ );
128
+ console.log(`${COLORS.GREEN}✔ Created infrastructure/common/middleware/auth.go${COLORS.NC}`);
129
+
130
+ // Auth repository
131
+ await createFile(
132
+ path.join(this.infrastructureDir, "repositories", "auth", "auth.repository.go"),
133
+ this.getAuthRepositoryContent()
134
+ );
135
+ console.log(`${COLORS.GREEN}✔ Created infrastructure/repositories/auth/auth.repository.go${COLORS.NC}`);
136
+ }
137
+
138
+ async generateUsecaseFiles() {
139
+ await createFile(
140
+ path.join(this.usecasesDir, "auth", "auth.usecase.go"),
141
+ this.getAuthUsecaseContent()
142
+ );
143
+ console.log(`${COLORS.GREEN}✔ Created usecases/auth/auth.usecase.go${COLORS.NC}`);
144
+ }
145
+
146
+ async updateGoMod() {
147
+ const goModPath = path.join(process.cwd(), "go.mod");
148
+ try {
149
+ let content = await fs.readFile(goModPath, "utf8");
150
+
151
+ // Add JWT dependency if not present
152
+ if (!content.includes("github.com/golang-jwt/jwt/v5")) {
153
+ content = content.replace(
154
+ /require \(/,
155
+ `require (\n\tgithub.com/golang-jwt/jwt/v5 v5.2.0`
156
+ );
157
+ await fs.writeFile(goModPath, content, "utf8");
158
+ console.log(`${COLORS.GREEN}✔ Added JWT dependency to go.mod${COLORS.NC}`);
159
+ }
160
+ } catch (error) {
161
+ console.log(`${COLORS.YELLOW}⚠ Could not update go.mod: ${error.message}${COLORS.NC}`);
162
+ }
163
+ }
164
+
165
+ printSummary() {
166
+ console.log(`
167
+ ${COLORS.CYAN}╔════════════════════════════════════════════════════════════╗
168
+ ║ Authentication Generated ║
169
+ ╠════════════════════════════════════════════════════════════╣
170
+ ║ 📁 Files created: ║
171
+ ║ • domain/models/auth.go ║
172
+ ║ • infrastructure/common/auth/jwt.go ║
173
+ ║ • infrastructure/common/auth/passport.go ║
174
+ ║ • infrastructure/common/auth/middleware.go ║
175
+ ║ • infrastructure/common/middleware/auth.go ║
176
+ ║ • infrastructure/repositories/auth/auth.repository.go ║
177
+ ║ • usecases/auth/auth.usecase.go ║
178
+ ╠════════════════════════════════════════════════════════════╣
179
+ ║ 🔧 Next steps: ║
180
+ ║ 1. Set JWT_SECRET in your .env file ║
181
+ ║ 2. Run: go mod tidy ║
182
+ ║ 3. Use auth.Usecase in your services ║
183
+ ╚════════════════════════════════════════════════════════════╝${COLORS.NC}
184
+ `);
185
+ }
186
+
187
+ // ============================================
188
+ // Content Generators
189
+ // ============================================
190
+
191
+ getAuthModelContent() {
192
+ return `package models
193
+
194
+ import (
195
+ "time"
196
+
197
+ "github.com/google/uuid"
198
+ )
199
+
200
+ // AuthUser represents the authenticated user
201
+ type AuthUser struct {
202
+ ID uuid.UUID \`json:"id"\`
203
+ Email string \`json:"email"\`
204
+ Role string \`json:"role"\`
205
+ IsActive bool \`json:"is_active"\`
206
+ }
207
+
208
+ // TokenPair represents access and refresh tokens
209
+ type TokenPair struct {
210
+ AccessToken string \`json:"access_token"\`
211
+ RefreshToken string \`json:"refresh_token"\`
212
+ ExpiresIn int64 \`json:"expires_in"\`
213
+ TokenType string \`json:"token_type"\`
214
+ }
215
+
216
+ // TokenClaims represents JWT claims
217
+ type TokenClaims struct {
218
+ UserID uuid.UUID \`json:"user_id"\`
219
+ Email string \`json:"email"\`
220
+ Role string \`json:"role"\`
221
+ TokenType string \`json:"token_type"\`
222
+ IssuedAt time.Time \`json:"iat"\`
223
+ ExpiresAt time.Time \`json:"exp"\`
224
+ }
225
+
226
+ // LoginRequest represents login credentials
227
+ type LoginRequest struct {
228
+ Email string \`json:"email"\`
229
+ Password string \`json:"password"\`
230
+ }
231
+
232
+ // RegisterRequest represents registration data
233
+ type RegisterRequest struct {
234
+ Email string \`json:"email"\`
235
+ Password string \`json:"password"\`
236
+ Name string \`json:"name"\`
237
+ }
238
+
239
+ // RefreshTokenRequest represents refresh token request
240
+ type RefreshTokenRequest struct {
241
+ RefreshToken string \`json:"refresh_token"\`
242
+ }
243
+
244
+ // AuthContextKey is the context key for auth user
245
+ type AuthContextKey string
246
+
247
+ // AuthUserKey is the context key for authenticated user
248
+ const AuthUserKey AuthContextKey = "authUser"
249
+
250
+ // TokenClaimsKey is the context key for token claims
251
+ const TokenClaimsKey AuthContextKey = "tokenClaims"
252
+ `;
253
+ }
254
+
255
+ getJwtContent() {
256
+ return `package auth
257
+
258
+ import (
259
+ "errors"
260
+ "os"
261
+ "time"
262
+
263
+ "${this.goModule}/src/domain/models"
264
+
265
+ "github.com/golang-jwt/jwt/v5"
266
+ "github.com/google/uuid"
267
+ )
268
+
269
+ var (
270
+ ErrInvalidToken = errors.New("invalid token")
271
+ ErrExpiredToken = errors.New("token has expired")
272
+ ErrInvalidSignature = errors.New("invalid token signature")
273
+ )
274
+
275
+ // JWTConfig holds JWT configuration
276
+ type JWTConfig struct {
277
+ SecretKey string
278
+ AccessTokenDuration time.Duration
279
+ RefreshTokenDuration time.Duration
280
+ Issuer string
281
+ }
282
+
283
+ // JWTService handles JWT operations
284
+ type JWTService struct {
285
+ config JWTConfig
286
+ }
287
+
288
+ // NewJWTService creates a new JWT service
289
+ func NewJWTService() *JWTService {
290
+ secret := os.Getenv("JWT_SECRET")
291
+ if secret == "" {
292
+ secret = "your-super-secret-key-change-in-production"
293
+ }
294
+
295
+ return &JWTService{
296
+ config: JWTConfig{
297
+ SecretKey: secret,
298
+ AccessTokenDuration: 15 * time.Minute,
299
+ RefreshTokenDuration: 7 * 24 * time.Hour,
300
+ Issuer: os.Getenv("JWT_ISSUER"),
301
+ },
302
+ }
303
+ }
304
+
305
+ // CustomClaims represents the JWT claims
306
+ type CustomClaims struct {
307
+ UserID string \`json:"user_id"\`
308
+ Email string \`json:"email"\`
309
+ Role string \`json:"role"\`
310
+ TokenType string \`json:"token_type"\`
311
+ jwt.RegisteredClaims
312
+ }
313
+
314
+ // GenerateTokenPair generates access and refresh tokens
315
+ func (s *JWTService) GenerateTokenPair(user *models.AuthUser) (*models.TokenPair, error) {
316
+ accessToken, err := s.generateToken(user, "access", s.config.AccessTokenDuration)
317
+ if err != nil {
318
+ return nil, err
319
+ }
320
+
321
+ refreshToken, err := s.generateToken(user, "refresh", s.config.RefreshTokenDuration)
322
+ if err != nil {
323
+ return nil, err
324
+ }
325
+
326
+ return &models.TokenPair{
327
+ AccessToken: accessToken,
328
+ RefreshToken: refreshToken,
329
+ ExpiresIn: int64(s.config.AccessTokenDuration.Seconds()),
330
+ TokenType: "Bearer",
331
+ }, nil
332
+ }
333
+
334
+ // generateToken creates a new JWT token
335
+ func (s *JWTService) generateToken(user *models.AuthUser, tokenType string, duration time.Duration) (string, error) {
336
+ now := time.Now()
337
+ claims := CustomClaims{
338
+ UserID: user.ID.String(),
339
+ Email: user.Email,
340
+ Role: user.Role,
341
+ TokenType: tokenType,
342
+ RegisteredClaims: jwt.RegisteredClaims{
343
+ Issuer: s.config.Issuer,
344
+ Subject: user.ID.String(),
345
+ IssuedAt: jwt.NewNumericDate(now),
346
+ ExpiresAt: jwt.NewNumericDate(now.Add(duration)),
347
+ NotBefore: jwt.NewNumericDate(now),
348
+ },
349
+ }
350
+
351
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
352
+ return token.SignedString([]byte(s.config.SecretKey))
353
+ }
354
+
355
+ // ValidateToken validates a JWT token and returns claims
356
+ func (s *JWTService) ValidateToken(tokenString string) (*models.TokenClaims, error) {
357
+ token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
358
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
359
+ return nil, ErrInvalidSignature
360
+ }
361
+ return []byte(s.config.SecretKey), nil
362
+ })
363
+
364
+ if err != nil {
365
+ if errors.Is(err, jwt.ErrTokenExpired) {
366
+ return nil, ErrExpiredToken
367
+ }
368
+ return nil, ErrInvalidToken
369
+ }
370
+
371
+ claims, ok := token.Claims.(*CustomClaims)
372
+ if !ok || !token.Valid {
373
+ return nil, ErrInvalidToken
374
+ }
375
+
376
+ userID, err := uuid.Parse(claims.UserID)
377
+ if err != nil {
378
+ return nil, ErrInvalidToken
379
+ }
380
+
381
+ return &models.TokenClaims{
382
+ UserID: userID,
383
+ Email: claims.Email,
384
+ Role: claims.Role,
385
+ TokenType: claims.TokenType,
386
+ IssuedAt: claims.IssuedAt.Time,
387
+ ExpiresAt: claims.ExpiresAt.Time,
388
+ }, nil
389
+ }
390
+
391
+ // RefreshAccessToken generates a new access token from a valid refresh token
392
+ func (s *JWTService) RefreshAccessToken(refreshToken string) (*models.TokenPair, error) {
393
+ claims, err := s.ValidateToken(refreshToken)
394
+ if err != nil {
395
+ return nil, err
396
+ }
397
+
398
+ if claims.TokenType != "refresh" {
399
+ return nil, ErrInvalidToken
400
+ }
401
+
402
+ user := &models.AuthUser{
403
+ ID: claims.UserID,
404
+ Email: claims.Email,
405
+ Role: claims.Role,
406
+ }
407
+
408
+ return s.GenerateTokenPair(user)
409
+ }
410
+ `;
411
+ }
412
+
413
+ getPassportContent() {
414
+ return `package auth
415
+
416
+ import (
417
+ "context"
418
+ "errors"
419
+
420
+ "${this.goModule}/src/domain/models"
421
+
422
+ "golang.org/x/crypto/bcrypt"
423
+ )
424
+
425
+ var (
426
+ ErrInvalidCredentials = errors.New("invalid credentials")
427
+ ErrUserNotFound = errors.New("user not found")
428
+ ErrUserInactive = errors.New("user account is inactive")
429
+ )
430
+
431
+ // Strategy defines the authentication strategy interface
432
+ type Strategy interface {
433
+ Authenticate(ctx context.Context, credentials interface{}) (*models.AuthUser, error)
434
+ Name() string
435
+ }
436
+
437
+ // Passport manages authentication strategies
438
+ type Passport struct {
439
+ strategies map[string]Strategy
440
+ jwtService *JWTService
441
+ }
442
+
443
+ // NewPassport creates a new passport instance
444
+ func NewPassport(jwtService *JWTService) *Passport {
445
+ return &Passport{
446
+ strategies: make(map[string]Strategy),
447
+ jwtService: jwtService,
448
+ }
449
+ }
450
+
451
+ // RegisterStrategy registers an authentication strategy
452
+ func (p *Passport) RegisterStrategy(strategy Strategy) {
453
+ p.strategies[strategy.Name()] = strategy
454
+ }
455
+
456
+ // Authenticate authenticates using the specified strategy
457
+ func (p *Passport) Authenticate(ctx context.Context, strategyName string, credentials interface{}) (*models.AuthUser, error) {
458
+ strategy, ok := p.strategies[strategyName]
459
+ if !ok {
460
+ return nil, errors.New("unknown authentication strategy: " + strategyName)
461
+ }
462
+ return strategy.Authenticate(ctx, credentials)
463
+ }
464
+
465
+ // GenerateTokens generates JWT tokens for the authenticated user
466
+ func (p *Passport) GenerateTokens(user *models.AuthUser) (*models.TokenPair, error) {
467
+ return p.jwtService.GenerateTokenPair(user)
468
+ }
469
+
470
+ // ============================================================================
471
+ // Local Strategy (Email/Password)
472
+ // ============================================================================
473
+
474
+ // UserFinder interface for finding users
475
+ type UserFinder interface {
476
+ FindByEmail(ctx context.Context, email string) (*models.AuthUser, string, error) // returns user, hashedPassword, error
477
+ }
478
+
479
+ // LocalStrategy implements email/password authentication
480
+ type LocalStrategy struct {
481
+ userFinder UserFinder
482
+ }
483
+
484
+ // NewLocalStrategy creates a new local strategy
485
+ func NewLocalStrategy(userFinder UserFinder) *LocalStrategy {
486
+ return &LocalStrategy{
487
+ userFinder: userFinder,
488
+ }
489
+ }
490
+
491
+ // Name returns the strategy name
492
+ func (s *LocalStrategy) Name() string {
493
+ return "local"
494
+ }
495
+
496
+ // Authenticate validates email and password
497
+ func (s *LocalStrategy) Authenticate(ctx context.Context, credentials interface{}) (*models.AuthUser, error) {
498
+ creds, ok := credentials.(*models.LoginRequest)
499
+ if !ok {
500
+ return nil, ErrInvalidCredentials
501
+ }
502
+
503
+ user, hashedPassword, err := s.userFinder.FindByEmail(ctx, creds.Email)
504
+ if err != nil {
505
+ return nil, ErrUserNotFound
506
+ }
507
+
508
+ if !user.IsActive {
509
+ return nil, ErrUserInactive
510
+ }
511
+
512
+ if err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(creds.Password)); err != nil {
513
+ return nil, ErrInvalidCredentials
514
+ }
515
+
516
+ return user, nil
517
+ }
518
+
519
+ // ============================================================================
520
+ // JWT Strategy (Token validation)
521
+ // ============================================================================
522
+
523
+ // JWTStrategy implements JWT token authentication
524
+ type JWTStrategy struct {
525
+ jwtService *JWTService
526
+ }
527
+
528
+ // NewJWTStrategy creates a new JWT strategy
529
+ func NewJWTStrategy(jwtService *JWTService) *JWTStrategy {
530
+ return &JWTStrategy{
531
+ jwtService: jwtService,
532
+ }
533
+ }
534
+
535
+ // Name returns the strategy name
536
+ func (s *JWTStrategy) Name() string {
537
+ return "jwt"
538
+ }
539
+
540
+ // Authenticate validates JWT token
541
+ func (s *JWTStrategy) Authenticate(ctx context.Context, credentials interface{}) (*models.AuthUser, error) {
542
+ token, ok := credentials.(string)
543
+ if !ok {
544
+ return nil, ErrInvalidToken
545
+ }
546
+
547
+ claims, err := s.jwtService.ValidateToken(token)
548
+ if err != nil {
549
+ return nil, err
550
+ }
551
+
552
+ return &models.AuthUser{
553
+ ID: claims.UserID,
554
+ Email: claims.Email,
555
+ Role: claims.Role,
556
+ }, nil
557
+ }
558
+
559
+ // ============================================================================
560
+ // Password Utilities
561
+ // ============================================================================
562
+
563
+ // HashPassword hashes a password using bcrypt
564
+ func HashPassword(password string) (string, error) {
565
+ bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
566
+ return string(bytes), err
567
+ }
568
+
569
+ // CheckPassword compares a password with its hash
570
+ func CheckPassword(password, hash string) bool {
571
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
572
+ return err == nil
573
+ }
574
+ `;
575
+ }
576
+
577
+ getMiddlewareContent() {
578
+ return `package auth
579
+
580
+ import (
581
+ "context"
582
+
583
+ "${this.goModule}/src/domain/models"
584
+ )
585
+
586
+ // GetAuthUserFromContext retrieves the authenticated user from context
587
+ func GetAuthUserFromContext(ctx context.Context) (*models.AuthUser, bool) {
588
+ user, ok := ctx.Value(models.AuthUserKey).(*models.AuthUser)
589
+ return user, ok
590
+ }
591
+
592
+ // GetTokenClaimsFromContext retrieves token claims from context
593
+ func GetTokenClaimsFromContext(ctx context.Context) (*models.TokenClaims, bool) {
594
+ claims, ok := ctx.Value(models.TokenClaimsKey).(*models.TokenClaims)
595
+ return claims, ok
596
+ }
597
+ `;
598
+ }
599
+
600
+ getAuthMiddlewareGinContent() {
601
+ return `package middleware
602
+
603
+ import (
604
+ "net/http"
605
+ "strings"
606
+
607
+ "${this.goModule}/src/domain/models"
608
+ "${this.goModule}/src/infrastructure/common/auth"
609
+ "${this.goModule}/src/infrastructure/common/response"
610
+
611
+ "github.com/gin-gonic/gin"
612
+ )
613
+
614
+ // AuthMiddleware provides JWT authentication middleware for Gin
615
+ func AuthMiddleware(jwtService *auth.JWTService) gin.HandlerFunc {
616
+ return func(c *gin.Context) {
617
+ authHeader := c.GetHeader("Authorization")
618
+ if authHeader == "" {
619
+ response.Error(c, http.StatusUnauthorized, "Missing authorization header")
620
+ c.Abort()
621
+ return
622
+ }
623
+
624
+ // Extract token from "Bearer <token>"
625
+ token := strings.TrimPrefix(authHeader, "Bearer ")
626
+ if token == authHeader {
627
+ response.Error(c, http.StatusUnauthorized, "Invalid authorization format")
628
+ c.Abort()
629
+ return
630
+ }
631
+
632
+ // Validate token
633
+ claims, err := jwtService.ValidateToken(token)
634
+ if err != nil {
635
+ if err == auth.ErrExpiredToken {
636
+ response.Error(c, http.StatusUnauthorized, "Token expired")
637
+ c.Abort()
638
+ return
639
+ }
640
+ response.Error(c, http.StatusUnauthorized, "Invalid token")
641
+ c.Abort()
642
+ return
643
+ }
644
+
645
+ // Create auth user from claims
646
+ authUser := &models.AuthUser{
647
+ ID: claims.UserID,
648
+ Email: claims.Email,
649
+ Role: claims.Role,
650
+ IsActive: true,
651
+ }
652
+
653
+ // Store user in context
654
+ c.Set(string(models.AuthUserKey), authUser)
655
+ c.Set(string(models.TokenClaimsKey), claims)
656
+
657
+ c.Next()
658
+ }
659
+ }
660
+
661
+ // GetAuthUser retrieves the authenticated user from Gin context
662
+ func GetAuthUser(c *gin.Context) (*models.AuthUser, bool) {
663
+ user, exists := c.Get(string(models.AuthUserKey))
664
+ if !exists {
665
+ return nil, false
666
+ }
667
+ authUser, ok := user.(*models.AuthUser)
668
+ return authUser, ok
669
+ }
670
+
671
+ // RequireRole middleware checks if user has required role
672
+ func RequireRole(roles ...string) gin.HandlerFunc {
673
+ return func(c *gin.Context) {
674
+ user, ok := GetAuthUser(c)
675
+ if !ok {
676
+ response.Error(c, http.StatusUnauthorized, "Not authenticated")
677
+ c.Abort()
678
+ return
679
+ }
680
+
681
+ for _, role := range roles {
682
+ if user.Role == role {
683
+ c.Next()
684
+ return
685
+ }
686
+ }
687
+
688
+ response.Error(c, http.StatusForbidden, "Insufficient permissions")
689
+ c.Abort()
690
+ }
691
+ }
692
+ `;
693
+ }
694
+
695
+ getAuthRepositoryContent() {
696
+ return `package auth
697
+
698
+ import (
699
+ "context"
700
+
701
+ "${this.goModule}/src/domain/models"
702
+
703
+ "github.com/google/uuid"
704
+ "gorm.io/gorm"
705
+ )
706
+
707
+ // Repository handles auth data operations
708
+ type Repository struct {
709
+ db *gorm.DB
710
+ }
711
+
712
+ // NewRepository creates a new auth repository
713
+ func NewRepository(db *gorm.DB) *Repository {
714
+ return &Repository{db: db}
715
+ }
716
+
717
+ // UserEntity represents the user in database (example - adjust to your user entity)
718
+ type UserEntity struct {
719
+ models.BaseModel
720
+ Email string \`gorm:"uniqueIndex;not null"\`
721
+ Password string \`gorm:"not null"\`
722
+ Name string
723
+ Role string \`gorm:"default:'user'"\`
724
+ }
725
+
726
+ // TableName returns the table name
727
+ func (UserEntity) TableName() string {
728
+ return "users"
729
+ }
730
+
731
+ // FindByEmail finds a user by email and returns auth user with hashed password
732
+ func (r *Repository) FindByEmail(ctx context.Context, email string) (*models.AuthUser, string, error) {
733
+ var user UserEntity
734
+ if err := r.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
735
+ return nil, "", err
736
+ }
737
+
738
+ authUser := &models.AuthUser{
739
+ ID: user.ID,
740
+ Email: user.Email,
741
+ Role: user.Role,
742
+ IsActive: user.IsActive,
743
+ }
744
+
745
+ return authUser, user.Password, nil
746
+ }
747
+
748
+ // FindByID finds a user by ID
749
+ func (r *Repository) FindByID(ctx context.Context, id uuid.UUID) (*models.AuthUser, error) {
750
+ var user UserEntity
751
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&user).Error; err != nil {
752
+ return nil, err
753
+ }
754
+
755
+ return &models.AuthUser{
756
+ ID: user.ID,
757
+ Email: user.Email,
758
+ Role: user.Role,
759
+ IsActive: user.IsActive,
760
+ }, nil
761
+ }
762
+
763
+ // CreateUser creates a new user
764
+ func (r *Repository) CreateUser(ctx context.Context, email, hashedPassword, name, role string) (*models.AuthUser, error) {
765
+ user := &UserEntity{
766
+ BaseModel: models.NewBaseModel(),
767
+ Email: email,
768
+ Password: hashedPassword,
769
+ Name: name,
770
+ Role: role,
771
+ }
772
+
773
+ if err := r.db.WithContext(ctx).Create(user).Error; err != nil {
774
+ return nil, err
775
+ }
776
+
777
+ return &models.AuthUser{
778
+ ID: user.ID,
779
+ Email: user.Email,
780
+ Role: user.Role,
781
+ IsActive: user.IsActive,
782
+ }, nil
783
+ }
784
+
785
+ // UpdatePassword updates user password
786
+ func (r *Repository) UpdatePassword(ctx context.Context, userID uuid.UUID, hashedPassword string) error {
787
+ return r.db.WithContext(ctx).Model(&UserEntity{}).
788
+ Where("id = ?", userID).
789
+ Update("password", hashedPassword).Error
790
+ }
791
+
792
+ // EmailExists checks if email already exists
793
+ func (r *Repository) EmailExists(ctx context.Context, email string) (bool, error) {
794
+ var count int64
795
+ err := r.db.WithContext(ctx).Model(&UserEntity{}).Where("email = ?", email).Count(&count).Error
796
+ return count > 0, err
797
+ }
798
+ `;
799
+ }
800
+
801
+ getAuthUsecaseContent() {
802
+ return `package auth
803
+
804
+ import (
805
+ "context"
806
+ "errors"
807
+
808
+ "${this.goModule}/src/domain/logger"
809
+ "${this.goModule}/src/domain/models"
810
+ authPkg "${this.goModule}/src/infrastructure/common/auth"
811
+ authRepo "${this.goModule}/src/infrastructure/repositories/auth"
812
+ )
813
+
814
+ var (
815
+ ErrEmailExists = errors.New("email already exists")
816
+ ErrInvalidPassword = errors.New("password must be at least 8 characters")
817
+ )
818
+
819
+ // Usecase handles authentication business logic
820
+ type Usecase struct {
821
+ repo *authRepo.Repository
822
+ passport *authPkg.Passport
823
+ jwtService *authPkg.JWTService
824
+ logger logger.Logger
825
+ }
826
+
827
+ // NewUsecase creates a new auth usecase
828
+ func NewUsecase(repo *authRepo.Repository, logger logger.Logger) *Usecase {
829
+ jwtService := authPkg.NewJWTService()
830
+ passport := authPkg.NewPassport(jwtService)
831
+
832
+ // Register local strategy
833
+ localStrategy := authPkg.NewLocalStrategy(repo)
834
+ passport.RegisterStrategy(localStrategy)
835
+
836
+ // Register JWT strategy
837
+ jwtStrategy := authPkg.NewJWTStrategy(jwtService)
838
+ passport.RegisterStrategy(jwtStrategy)
839
+
840
+ return &Usecase{
841
+ repo: repo,
842
+ passport: passport,
843
+ jwtService: jwtService,
844
+ logger: logger,
845
+ }
846
+ }
847
+
848
+ // Login authenticates user and returns tokens
849
+ func (u *Usecase) Login(ctx context.Context, req *models.LoginRequest) (*models.TokenPair, error) {
850
+ user, err := u.passport.Authenticate(ctx, "local", req)
851
+ if err != nil {
852
+ u.logger.Error("AuthUsecase", "Login failed", err)
853
+ return nil, err
854
+ }
855
+
856
+ tokens, err := u.passport.GenerateTokens(user)
857
+ if err != nil {
858
+ u.logger.Error("AuthUsecase", "Token generation failed", err)
859
+ return nil, err
860
+ }
861
+
862
+ u.logger.Info("AuthUsecase", "User logged in: "+user.Email)
863
+ return tokens, nil
864
+ }
865
+
866
+ // Register creates a new user and returns tokens
867
+ func (u *Usecase) Register(ctx context.Context, req *models.RegisterRequest) (*models.TokenPair, error) {
868
+ // Validate password
869
+ if len(req.Password) < 8 {
870
+ return nil, ErrInvalidPassword
871
+ }
872
+
873
+ // Check if email exists
874
+ exists, err := u.repo.EmailExists(ctx, req.Email)
875
+ if err != nil {
876
+ u.logger.Error("AuthUsecase", "Email check failed", err)
877
+ return nil, err
878
+ }
879
+ if exists {
880
+ return nil, ErrEmailExists
881
+ }
882
+
883
+ // Hash password
884
+ hashedPassword, err := authPkg.HashPassword(req.Password)
885
+ if err != nil {
886
+ u.logger.Error("AuthUsecase", "Password hashing failed", err)
887
+ return nil, err
888
+ }
889
+
890
+ // Create user
891
+ user, err := u.repo.CreateUser(ctx, req.Email, hashedPassword, req.Name, "user")
892
+ if err != nil {
893
+ u.logger.Error("AuthUsecase", "User creation failed", err)
894
+ return nil, err
895
+ }
896
+
897
+ // Generate tokens
898
+ tokens, err := u.passport.GenerateTokens(user)
899
+ if err != nil {
900
+ u.logger.Error("AuthUsecase", "Token generation failed", err)
901
+ return nil, err
902
+ }
903
+
904
+ u.logger.Info("AuthUsecase", "User registered: "+user.Email)
905
+ return tokens, nil
906
+ }
907
+
908
+ // RefreshToken refreshes the access token
909
+ func (u *Usecase) RefreshToken(ctx context.Context, refreshToken string) (*models.TokenPair, error) {
910
+ tokens, err := u.jwtService.RefreshAccessToken(refreshToken)
911
+ if err != nil {
912
+ u.logger.Error("AuthUsecase", "Token refresh failed", err)
913
+ return nil, err
914
+ }
915
+
916
+ u.logger.Info("AuthUsecase", "Token refreshed")
917
+ return tokens, nil
918
+ }
919
+
920
+ // ValidateToken validates a token and returns the user
921
+ func (u *Usecase) ValidateToken(ctx context.Context, token string) (*models.AuthUser, error) {
922
+ user, err := u.passport.Authenticate(ctx, "jwt", token)
923
+ if err != nil {
924
+ return nil, err
925
+ }
926
+ return user, nil
927
+ }
928
+
929
+ // GetJWTService returns the JWT service for middleware use
930
+ func (u *Usecase) GetJWTService() *authPkg.JWTService {
931
+ return u.jwtService
932
+ }
933
+ `;
934
+ }
935
+ }
936
+
937
+ module.exports = AuthGenerator;