package google_auth import ( "context" "fmt" "os" "strings" firebase "firebase.google.com/go/v4" "firebase.google.com/go/v4/auth" "github.com/coze-dev/coze-go" "google.golang.org/api/option" ) // newSys 创建 Google 认证系统实例。 // 参数: // - options: 运行所需配置(如服务账号密钥路径) // // 返回值: // - sys: 创建成功的实例 // - err: 创建失败时返回错误 func newSys(options Options) (sys *Google, err error) { // 密钥在每次 Auth 时才真正读取(支持热替换文件),但**能不能用**要在启动时就说清楚: // 原先这里无条件返回成功,日志打「init sys.google_auth success!」,而配置里的 json 根本不存在, // 直到有用户点 Google 登录才失败——启动日志与实际能力相反,比没有日志更糟。 // 判定只做「配得上」这一层:空配置、或写的是路径而文件不在,都算未配置;内嵌 JSON 与 // 文件内容是否合法留给调用时报错(那属于凭据本身的问题,不是配置缺失)。 cred := strings.TrimSpace(options.ApiKeyFile) switch { case cred == "": return nil, fmt.Errorf("google_auth: 未配置服务账号密钥(ApiKeyFile)") case !strings.HasPrefix(cred, "{"): if _, e := os.Stat(cred); e != nil { return nil, fmt.Errorf("google_auth: 服务账号密钥文件不存在: %s", cred) } } sys = &Google{ options: options, } return } type Google struct { options Options api coze.CozeAPI } // Auth 使用 Firebase Admin SDK 校验 Google ID Token。 // 参数: // - ctx: 上下文 // - idToken: 客户端传入的 Google/Firebase ID Token // // 返回值: // - info: 校验成功后返回的 Token 信息 // - err: 校验失败或邮箱未验证时返回错误 func (this *Google) Auth(ctx context.Context, idToken string) (info *auth.Token, err error) { // 服务账号密钥:以 { 开头视为直接内嵌的 JSON 内容(推荐,配置全在库/env),否则当作文件路径。 var opt option.ClientOption if cred := strings.TrimSpace(this.options.ApiKeyFile); strings.HasPrefix(cred, "{") { opt = option.WithCredentialsJSON([]byte(this.options.ApiKeyFile)) } else { opt = option.WithCredentialsFile(this.options.ApiKeyFile) } // 初始化 Firebase App app, err := firebase.NewApp(ctx, nil, opt) if err != nil { return nil, fmt.Errorf("failed to initialize Firebase app: %v", err) } // 获取 Auth 客户端 client, err := app.Auth(ctx) if err != nil { return nil, fmt.Errorf("failed to get Auth client: %v", err) } // 验证 Token token, err := client.VerifyIDToken(ctx, idToken) if err != nil { return nil, fmt.Errorf("invalid ID Token: %v", err) } // 检查邮箱是否已验证(仅当 email_verified 明确为 false 时拒绝;缺失/非 bool 不再导致 panic) // if v, ok := token.Claims["email_verified"]; ok && v != nil { // if verified, ok := v.(bool); ok && !verified { // return nil, fmt.Errorf("email not verified") // } // } // log.Printf("Verified UID: %s, Email: %s", token.UID, token.Claims["email"]) return token, nil }