ultisuite-backend/internal/migration/google_dwd.go
R3D347HR4Y 7143a36c19
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(mail): integrate Stalwart hosted mail and migration features
- Added configuration options for Stalwart hosted mail in .env.example.
- Updated Docker Compose to include Stalwart service with health checks.
- Introduced new API endpoints for managing mail domains and migration projects.
- Enhanced Authentik blueprints for user enrollment and post-migration security.
- Updated OAuth handling for Google and Microsoft migration processes.
- Improved error handling and response structures in the mail API.
- Added integration tests for email claiming and migration workflows.
2026-06-13 12:47:08 +02:00

78 lines
2.0 KiB
Go

package migration
import (
"context"
"encoding/json"
"fmt"
"strings"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
)
var googleDWDScopes = []string{
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/contacts.readonly",
}
// GoogleDWD mints access tokens via a service account with domain-wide delegation.
type GoogleDWD struct {
jwtConfig *jwt.Config
}
func NewGoogleDWD(jsonKey string) (*GoogleDWD, error) {
jsonKey = strings.TrimSpace(jsonKey)
if jsonKey == "" {
return nil, nil
}
conf, err := google.JWTConfigFromJSON([]byte(jsonKey), googleDWDScopes...)
if err != nil {
return nil, fmt.Errorf("parse google service account: %w", err)
}
return &GoogleDWD{jwtConfig: conf}, nil
}
func (g *GoogleDWD) Enabled() bool {
return g != nil && g.jwtConfig != nil
}
func (g *GoogleDWD) AccessToken(ctx context.Context, subjectEmail string) (string, error) {
if !g.Enabled() {
return "", fmt.Errorf("google domain-wide delegation not configured")
}
subjectEmail = strings.ToLower(strings.TrimSpace(subjectEmail))
if subjectEmail == "" {
return "", fmt.Errorf("subject email required for domain-wide delegation")
}
conf := *g.jwtConfig
conf.Subject = subjectEmail
token, err := conf.TokenSource(ctx).Token()
if err != nil {
return "", fmt.Errorf("google dwd token: %w", err)
}
if token.AccessToken == "" {
return "", fmt.Errorf("google dwd token empty")
}
return token.AccessToken, nil
}
func validateServiceAccountJSON(raw string) error {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil
}
var probe struct {
ClientEmail string `json:"client_email"`
PrivateKey string `json:"private_key"`
}
if err := json.Unmarshal([]byte(raw), &probe); err != nil {
return fmt.Errorf("invalid service account json: %w", err)
}
if probe.ClientEmail == "" || probe.PrivateKey == "" {
return fmt.Errorf("service account json missing client_email or private_key")
}
return nil
}