package github import ( "context" "encoding/json" "fmt" "net/http" "net/url" "github.com/komari-monitor/komari/database/config" "github.com/komari-monitor/komari/utils" "github.com/komari-monitor/komari/utils/oauth/factory" "github.com/patrickmn/go-cache" ) func init() { } func (g *Github) GetName() string { return "github" } func (g *Github) GetConfiguration() factory.Configuration { return &g.Addition } func (g *Github) GetAuthorizationURL() string { state := utils.GenerateRandomString(16) // 构建GitHub OAuth授权URL authURL := fmt.Sprintf( "https://github.com/login/oauth/authorize?client_id=%s&state=%s&scope=user:email", url.QueryEscape(g.Addition.ClientId), url.QueryEscape(state), ) return authURL } func (g *Github) OnCallback(ctx context.Context, query map[string]string) (factory.OidcCallback, error) { code := query["code"] state := query["state"] cfg, _ := config.Get() // 验证state防止CSRF攻击 // state, _ := c.Cookie("oauth_state") if g.stateCache == nil { return factory.OidcCallback{}, fmt.Errorf("state cache not initialized") } if _, ok := g.stateCache.Get(state); !ok { return factory.OidcCallback{}, fmt.Errorf("invalid state") } if state == "" { return factory.OidcCallback{}, fmt.Errorf("invalid state") } // 获取code //code := c.Query("code") if code == "" { return factory.OidcCallback{}, fmt.Errorf("no code provided") } // 获取访问令牌 tokenURL := "https://github.com/login/oauth/access_token" data := url.Values{ "client_id": {cfg.OAuthClientID}, "client_secret": {cfg.OAuthClientSecret}, "code": {code}, } req, _ := http.NewRequest("POST", tokenURL, nil) req.URL.RawQuery = data.Encode() req.Header.Set("Accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return factory.OidcCallback{}, fmt.Errorf("failed to get access token: %v", err) } defer resp.Body.Close() var tokenResp struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` Scope string `json:"scope"` } if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { return factory.OidcCallback{}, fmt.Errorf("failed to parse access token response: %v", err) } // 获取用户信息 userReq, _ := http.NewRequest("GET", "https://api.github.com/user", nil) userReq.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) userReq.Header.Set("Accept", "application/json") userResp, err := http.DefaultClient.Do(userReq) if err != nil { return factory.OidcCallback{}, fmt.Errorf("failed to get user info: %v", err) } defer userResp.Body.Close() var githubUser GitHubUser if err := json.NewDecoder(userResp.Body).Decode(&githubUser); err != nil { return factory.OidcCallback{}, fmt.Errorf("failed to parse user info response: %v", err) } return factory.OidcCallback{UserId: fmt.Sprintf("%d", githubUser.ID)}, nil } func (g *Github) Init() error { g.stateCache = cache.New(cache.NoExpiration, cache.NoExpiration) return nil } func (g *Github) Destroy() error { g.stateCache.Flush() return nil } var _ factory.IOidcProvider = (*Github)(nil)