- 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.
78 lines
2.0 KiB
Go
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
|
|
}
|