[Go, Gin, Gorm におけるセキュアなモデル作成の方法🐭🚀


## 1. はじめに

- これはECサイトのオペレーター管理システムにおける中核となるモデル実装である
- システム管理者（system_admin）と店舗管理者（store_admin）の2つの権限を持つユーザーを管理し、セキュアなパスワード管理とデータの整合性を保証する

### 環境構成

主要な技術スタック：
Go v1.23.3
Gin Web Framework v1.10.0
GORM v1.25.12
PostgreSQL v17.0

https://gorm.io/index.html


## 2. モデル設計の特徴
### 2.1 権限管理システム
```go
const (
    RoleSystemAdmin = "system_admin" // システム管理者権限
    RoleStoreAdmin  = "store_admin"  // 店舗管理者権限
)
```

定数として権限を定義することで、タイプセーフな権限管理を実現している
- 権限値の誤入力を防止
- コード補完による開発効率の向上
- 権限の一元管理が可能

### 2.2 詳細なモデル構造

```go
type Operator struct {
    ID           string         `gorm:"type:uuid;primary_key" json:"id"`
    Email        string         `gorm:"unique;not null" json:"email" validate:"required,email"`
    Username     string         `gorm:"not null" json:"username" validate:"required"`
    PasswordHash string         `gorm:"not null" json:"password_hash" validate:"required,password"`
    Role         string         `gorm:"not null" json:"role" validate:"required,oneof=system_admin store_admin"`
    StoreID      string         `gorm:"type:uuid;not null" json:"store_id" validate:"required,uuid"`
    AvatarURL    string         `json:"avatar_url" validate:"omitempty,url"`
    CreatedBy    string         `gorm:"type:uuid;not null" json:"created_by" validate:"required,uuid"`
    UpdatedBy    string         `gorm:"type:uuid" json:"updated_by" validate:"required,uuid"`
    DeletedBy    *string        `gorm:"type:uuid" json:"deleted_by,omitempty" validate:"omitempty,uuid"`
    CreatedAt    time.Time      `gorm:"autoCreateTime;not null" json:"created_at"`
    UpdatedAt    time.Time      `gorm:"autoUpdateTime" json:"updated_at"`
    DeletedAt    gorm.DeletedAt `gorm:"index" json:"deleted_at"`
    Store        Store          `gorm:"foreignKey:StoreID" json:"store"`
}
```

https://pkg.go.dev/github.com/go-playground/validator/v10

https://pkg.go.dev/github.com/google/uuid

#### 2.2.1 主要フィールドの詳細解説

1. **識別子管理**
   - `ID`: UUIDv7形式を採用し、グローバルでユニークな識別子を保証
   - データベース移行やシステム統合時の整合性を確保

2. **認証情報**
   - `Email`: ユニーク制約付きで重複登録を防止
   - `PasswordHash`: ハッシュ化されたパスワードを保存し、平文での保存を回避
   - `Username`: システム内での表示名として使用

3. **権限とアクセス制御**
   - `Role`: system_admin/store_adminの2値のみを許容
   - `StoreID`: 所属店舗との紐付けによる適切なアクセス制御
   - `Store`: 店舗情報との1対1のリレーション

4. **監査証跡**
   - `CreatedBy`/`UpdatedBy`/`DeletedBy`: 操作者のトレーサビリティを確保
   - `CreatedAt`/`UpdatedAt`: 自動タイムスタンプによる操作日時の記録
   - `DeletedAt`: 論理削除のサポート

5. **プロフィール情報**
   - `AvatarURL`: オプショナルなプロフィール画像のURL

### 2.3 バリデーション機能

各フィールドに対して厳密なバリデーションルールを適用：

```go
validate:"required,email"           // メールアドレスの形式検証
validate:"required,password"        // カスタムパスワードルール
validate:"required,oneof=system_admin store_admin"  // 権限値の制限
validate:"required,uuid"            // UUID形式の検証
validate:"omitempty,url"           // URLフォーマットの検証
```

## 3. パスワードセキュリティ

### 3.1 パスワードポリシー
厳格なパスワードポリシーを実装し、セキュリティ要件を満たしている：

```go
const minPasswordLength = 8

func validatePassword(fl validator.FieldLevel) bool {
    password := fl.Field().String()
    if len(password) < minPasswordLength {
        return false
    }
    return hasRequiredCharacterTypes(password)
}
```

今回のパスワードは以下の要件を全て満たす
- 最小8文字以上の長さ
- 大文字（A-Z）を1文字以上含む
- 小文字（a-z）を1文字以上含む
- 数字（0-9）を1文字以上含む
- 記号（!@#$等）を1文字以上含む

### 3.2 文字種別チェックの実装

```go
func hasRequiredCharacterTypes(password string) bool {
    var (
        hasUpper, hasLower, hasNumber, hasSymbol bool
    )

    for _, char := range password {
        switch {
        case unicode.IsUpper(char):
            hasUpper = true
        case unicode.IsLower(char):
            hasLower = true
        case unicode.IsNumber(char):
            hasNumber = true
        case unicode.IsPunct(char) || unicode.IsSymbol(char):
            hasSymbol = true
        }

        if hasUpper && hasLower && hasNumber && hasSymbol {
            return true
        }
    }
    return false
}
```

このチェック機能により：
- 効率的な文字種別判定（1回のループで全条件をチェック）
- 早期リターンによるパフォーマンス最適化
- Unicode対応による多言語サポート

## 4. データベース連携

### 4.1 UUIDの自動生成
```go
func (o *Operator) BeforeCreate(tx *gorm.DB) error {
    if o.ID != "" {
        return nil
    }
    id, err := uuid.NewV7()
    if err != nil {
        return err
    }
    o.ID = id.String()
    return nil
}
```

GORMのフックを利用して：
- レコード作成前に自動的にUUIDを生成
- 既存IDの上書きを防止
- エラーハンドリングによる信頼性確保

https://v1.gorm.io/ja_JP/docs/hooks.html

### 4.2 データベース制約
- `gorm:"type:uuid;primary_key"`: UUIDタイプの主キー
- `gorm:"unique;not null"`: ユニーク制約と非NULL制約
- `gorm:"index"`: インデックスによる検索最適化
- `gorm:"foreignKey:StoreID"`: 外部キー制約による参照整合性の確保

## 5. 拡張性と保守性

このモデルは以下の特徴により、高い拡張性と保守性を実現しています：

1. **モジュール性**
   - 各機能が明確に分離され、独立してテスト可能
   - カスタムバリデーションの追加が容易

2. **型安全性**
   - 厳密な型チェックによるバグの早期発見
   - コンパイル時のエラーチェック

3. **監査対応**
   - 完全な操作履歴の追跡が可能
   - コンプライアンス要件への対応

4. **セキュリティ**
   - パスワードの安全な管理
   - アクセス制御の適切な実装

## 6. まとめ
- このモデルは、セキュリティ、保守性、拡張性のバランスが取れた実装となっており、実際のプロダクション環境での使用に適している
- また、GORMやvalidatorなどの標準的なライブラリを活用することで、信頼性の高いシステムを効率的に構築することができる
- 今後の開発においても、このモデルを基盤として、さらなる機能拡張や改善を行うことが可能
- セキュリティ要件の変更やビジネスルールの追加にも、柔軟に対応できる設計となっている