You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
3.0 KiB
91 lines
3.0 KiB
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
|
|
}
|
|
|