go-duck-cli 1.1.42 → 1.1.43
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.
- package/generators/kratos.js +12 -12
- package/generators/multitenancy.js +28 -17
- package/package.json +1 -1
- package/templates/go/router.go.hbs +7 -0
package/generators/kratos.js
CHANGED
|
@@ -273,6 +273,8 @@ import (
|
|
|
273
273
|
my_middleware "{{projectName}}/middleware"
|
|
274
274
|
"gorm.io/gorm"
|
|
275
275
|
"go.mongodb.org/mongo-driver/mongo"
|
|
276
|
+
"google.golang.org/grpc/codes"
|
|
277
|
+
"google.golang.org/grpc/status"
|
|
276
278
|
"strings"
|
|
277
279
|
"fmt"
|
|
278
280
|
"sort"
|
|
@@ -297,13 +299,14 @@ func TenantServerInterceptor(conf *config.Config, db *gorm.DB) middleware.Middle
|
|
|
297
299
|
rolesInterface, ok := ra["roles"].([]interface{})
|
|
298
300
|
if !ok { return handler(ctx, req) }
|
|
299
301
|
|
|
302
|
+
superAdminRole := strings.ToLower(conf.GoDuck.Security.SuperAdminRole)
|
|
300
303
|
// Normalize roles (lowercase)
|
|
301
304
|
var lowerRoles []string
|
|
302
305
|
isAdmin := false
|
|
303
306
|
for _, r := range rolesInterface {
|
|
304
307
|
roleStr := strings.ToLower(fmt.Sprintf("%v", r))
|
|
305
308
|
lowerRoles = append(lowerRoles, roleStr)
|
|
306
|
-
if roleStr == "admin" || roleStr == "role_admin" { isAdmin = true }
|
|
309
|
+
if roleStr == "admin" || roleStr == "role_admin" || (superAdminRole != "" && roleStr == superAdminRole) { isAdmin = true }
|
|
307
310
|
}
|
|
308
311
|
|
|
309
312
|
var requestedTenant string
|
|
@@ -312,18 +315,12 @@ func TenantServerInterceptor(conf *config.Config, db *gorm.DB) middleware.Middle
|
|
|
312
315
|
}
|
|
313
316
|
|
|
314
317
|
var mappings []models.TenantRole
|
|
315
|
-
if
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ? AND tenant_id = ?", lowerRoles, requestedTenant).Scan(&mappings)
|
|
320
|
-
}
|
|
318
|
+
if isAdmin {
|
|
319
|
+
mappings = []models.TenantRole{}
|
|
320
|
+
} else if requestedTenant != "" {
|
|
321
|
+
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ? AND tenant_id = ?", lowerRoles, requestedTenant).Scan(&mappings)
|
|
321
322
|
} else {
|
|
322
|
-
|
|
323
|
-
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles").Scan(&mappings)
|
|
324
|
-
} else {
|
|
325
|
-
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ?", lowerRoles).Scan(&mappings)
|
|
326
|
-
}
|
|
323
|
+
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ?", lowerRoles).Scan(&mappings)
|
|
327
324
|
}
|
|
328
325
|
|
|
329
326
|
siloConnections := make(map[string]*gorm.DB)
|
|
@@ -340,6 +337,9 @@ func TenantServerInterceptor(conf *config.Config, db *gorm.DB) middleware.Middle
|
|
|
340
337
|
mappings = authorizedMappings
|
|
341
338
|
|
|
342
339
|
if len(mappings) == 0 {
|
|
340
|
+
if !isAdmin {
|
|
341
|
+
return nil, status.Error(codes.PermissionDenied, "Access denied. No tenant database provisioned for your roles.")
|
|
342
|
+
}
|
|
343
343
|
conn, _ := mgr.GetDB(fallbackDB)
|
|
344
344
|
siloConnections["fallback"] = conn
|
|
345
345
|
ctx = context.WithValue(ctx, "tenantDBConn", conn)
|
|
@@ -147,12 +147,13 @@ func TenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
147
147
|
return
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
superAdminRole := strings.ToLower(cfg.GoDuck.Security.SuperAdminRole)
|
|
150
151
|
var lowerRoles []string
|
|
151
152
|
isAdmin := false
|
|
152
153
|
for _, r := range roles {
|
|
153
154
|
roleStr := strings.ToLower(fmt.Sprintf("%v", r))
|
|
154
155
|
lowerRoles = append(lowerRoles, roleStr)
|
|
155
|
-
if roleStr == "admin" || roleStr == "role_admin" {
|
|
156
|
+
if roleStr == "admin" || roleStr == "role_admin" || (superAdminRole != "" && roleStr == superAdminRole) {
|
|
156
157
|
isAdmin = true
|
|
157
158
|
}
|
|
158
159
|
}
|
|
@@ -173,18 +174,12 @@ func TenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ? AND tenant_id IN ?", lowerRoles, requestedTenants).Scan(&mappings)
|
|
181
|
-
}
|
|
177
|
+
if isAdmin {
|
|
178
|
+
mappings = []models.TenantRole{}
|
|
179
|
+
} else if len(requestedTenants) > 0 {
|
|
180
|
+
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ? AND tenant_id IN ?", lowerRoles, requestedTenants).Scan(&mappings)
|
|
182
181
|
} else {
|
|
183
|
-
|
|
184
|
-
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles").Scan(&mappings)
|
|
185
|
-
} else {
|
|
186
|
-
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ?", lowerRoles).Scan(&mappings)
|
|
187
|
-
}
|
|
182
|
+
db.Raw("SELECT role_name, db_name, is_primary FROM tenant_roles WHERE LOWER(role_name) IN ?", lowerRoles).Scan(&mappings)
|
|
188
183
|
}
|
|
189
184
|
|
|
190
185
|
// Filter out unauthorized mappings (e.g. admin_db for non-admins)
|
|
@@ -198,11 +193,16 @@ func TenantMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
|
|
198
193
|
mappings = authorizedMappings
|
|
199
194
|
|
|
200
195
|
if len(mappings) == 0 {
|
|
196
|
+
if !isAdmin {
|
|
197
|
+
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied. No tenant database provisioned for your roles."})
|
|
198
|
+
c.Abort()
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
201
|
conn, err := mgr.GetDB(fallbackDB)
|
|
202
202
|
if err != nil || conn == nil {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to resolve tenant fallback database connection"})
|
|
204
|
+
c.Abort()
|
|
205
|
+
return
|
|
206
206
|
}
|
|
207
207
|
siloConnections["fallback"] = conn
|
|
208
208
|
c.Set("tenantDBConn", conn)
|
|
@@ -355,6 +355,7 @@ import (
|
|
|
355
355
|
)
|
|
356
356
|
|
|
357
357
|
type DatabaseRequest struct {
|
|
358
|
+
TenantID string \`json:"tenantId" form:"tenantId"\`
|
|
358
359
|
RoleName string \`json:"roleName" form:"roleName" binding:"required"\`
|
|
359
360
|
DBName string \`json:"dbName" form:"dbName" binding:"required"\`
|
|
360
361
|
IsPrimary bool \`json:"isPrimary" form:"isPrimary"\`
|
|
@@ -390,15 +391,25 @@ func CreateDatabaseAndMigrate(masterDB *gorm.DB) gin.HandlerFunc {
|
|
|
390
391
|
if count == 0 {
|
|
391
392
|
isPrimary = true
|
|
392
393
|
}
|
|
394
|
+
|
|
395
|
+
tenantID := req.TenantID
|
|
396
|
+
if tenantID == "" {
|
|
397
|
+
tenantID = uuid.New().String()
|
|
398
|
+
}
|
|
399
|
+
|
|
393
400
|
if err := masterDB.Where("LOWER(role_name) = LOWER(?) AND db_name = ?", req.RoleName, req.DBName).First(&existing).Error; err == nil {
|
|
394
401
|
if count <= 1 {
|
|
395
402
|
isPrimary = true
|
|
396
403
|
}
|
|
397
404
|
existing.IsPrimary = isPrimary
|
|
405
|
+
if req.TenantID != "" {
|
|
406
|
+
existing.TenantID = req.TenantID
|
|
407
|
+
}
|
|
398
408
|
masterDB.Save(&existing)
|
|
409
|
+
tenantID = existing.TenantID
|
|
399
410
|
} else {
|
|
400
411
|
masterDB.Create(&models.TenantRole{
|
|
401
|
-
TenantID:
|
|
412
|
+
TenantID: tenantID,
|
|
402
413
|
RoleName: req.RoleName,
|
|
403
414
|
DBName: req.DBName,
|
|
404
415
|
IsPrimary: isPrimary,
|
|
@@ -414,7 +425,7 @@ func CreateDatabaseAndMigrate(masterDB *gorm.DB) gin.HandlerFunc {
|
|
|
414
425
|
migrations.RunGoNativeMigrationsForTenant(tenantDB)
|
|
415
426
|
}
|
|
416
427
|
|
|
417
|
-
c.JSON(http.StatusOK, gin.H{"message": "Role silo assigned successfully", "role": req.RoleName, "primary": isPrimary})
|
|
428
|
+
c.JSON(http.StatusOK, gin.H{"message": "Role silo assigned successfully", "role": req.RoleName, "tenantId": tenantID, "primary": isPrimary})
|
|
418
429
|
}
|
|
419
430
|
}
|
|
420
431
|
|
package/package.json
CHANGED
|
@@ -230,6 +230,13 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
230
230
|
api := r.Group("/api")
|
|
231
231
|
api.Use(middleware.JWTMiddleware())
|
|
232
232
|
api.Use(middleware.TenantMiddleware(masterDB, appConfig))
|
|
233
|
+
// Log request DB resolution for debugging
|
|
234
|
+
api.Use(func(c *gin.Context) {
|
|
235
|
+
if dbName, ok := c.Get("resolvedDB"); ok {
|
|
236
|
+
fmt.Printf("[LOG] %s %s using DB: %s\n", c.Request.Method, c.Request.URL.Path, dbName.(string))
|
|
237
|
+
}
|
|
238
|
+
c.Next()
|
|
239
|
+
})
|
|
233
240
|
api.Use(middleware.AuditMiddleware(masterDB))
|
|
234
241
|
api.Use(middleware.MeteringMiddleware(masterDB))
|
|
235
242
|
{
|