| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- package oauth
- import (
- "context"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/i18n"
- "github.com/QuantumNous/new-api/logger"
- "github.com/QuantumNous/new-api/model"
- "github.com/gin-gonic/gin"
- )
- func init() {
- Register("linuxdo", &LinuxDOProvider{})
- }
- // LinuxDOProvider implements OAuth for Linux DO
- type LinuxDOProvider struct{}
- type linuxdoUser struct {
- Id int `json:"id"`
- Username string `json:"username"`
- Name string `json:"name"`
- Active bool `json:"active"`
- TrustLevel int `json:"trust_level"`
- Silenced bool `json:"silenced"`
- }
- func (p *LinuxDOProvider) GetName() string {
- return "Linux DO"
- }
- func (p *LinuxDOProvider) IsEnabled() bool {
- return common.LinuxDOOAuthEnabled
- }
- func (p *LinuxDOProvider) ExchangeToken(ctx context.Context, code string, c *gin.Context) (*OAuthToken, error) {
- if code == "" {
- return nil, NewOAuthError(i18n.MsgOAuthInvalidCode, nil)
- }
- logger.LogDebug(ctx, "[OAuth-LinuxDO] ExchangeToken: code=%s...", code[:min(len(code), 10)])
- // Get access token using Basic auth
- tokenEndpoint := common.GetEnvOrDefaultString("LINUX_DO_TOKEN_ENDPOINT", "https://connect.linux.do/oauth2/token")
- credentials := common.LinuxDOClientId + ":" + common.LinuxDOClientSecret
- basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(credentials))
- // Get redirect URI from request
- scheme := "http"
- if c.Request.TLS != nil {
- scheme = "https"
- }
- redirectURI := fmt.Sprintf("%s://%s/api/oauth/linuxdo", scheme, c.Request.Host)
- logger.LogDebug(ctx, "[OAuth-LinuxDO] ExchangeToken: token_endpoint=%s, redirect_uri=%s", tokenEndpoint, redirectURI)
- data := url.Values{}
- data.Set("grant_type", "authorization_code")
- data.Set("code", code)
- data.Set("redirect_uri", redirectURI)
- req, err := http.NewRequestWithContext(ctx, "POST", tokenEndpoint, strings.NewReader(data.Encode()))
- if err != nil {
- return nil, err
- }
- req.Header.Set("Authorization", basicAuth)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("Accept", "application/json")
- client := http.Client{Timeout: 5 * time.Second}
- res, err := client.Do(req)
- if err != nil {
- logger.LogError(ctx, fmt.Sprintf("[OAuth-LinuxDO] ExchangeToken error: %s", err.Error()))
- return nil, NewOAuthErrorWithRaw(i18n.MsgOAuthConnectFailed, map[string]any{"Provider": "Linux DO"}, err.Error())
- }
- defer res.Body.Close()
- logger.LogDebug(ctx, "[OAuth-LinuxDO] ExchangeToken response status: %d", res.StatusCode)
- var tokenRes struct {
- AccessToken string `json:"access_token"`
- Message string `json:"message"`
- }
- if err := json.NewDecoder(res.Body).Decode(&tokenRes); err != nil {
- logger.LogError(ctx, fmt.Sprintf("[OAuth-LinuxDO] ExchangeToken decode error: %s", err.Error()))
- return nil, err
- }
- if tokenRes.AccessToken == "" {
- logger.LogError(ctx, fmt.Sprintf("[OAuth-LinuxDO] ExchangeToken failed: %s", tokenRes.Message))
- return nil, NewOAuthErrorWithRaw(i18n.MsgOAuthTokenFailed, map[string]any{"Provider": "Linux DO"}, tokenRes.Message)
- }
- logger.LogDebug(ctx, "[OAuth-LinuxDO] ExchangeToken success")
- return &OAuthToken{
- AccessToken: tokenRes.AccessToken,
- }, nil
- }
- func (p *LinuxDOProvider) GetUserInfo(ctx context.Context, token *OAuthToken) (*OAuthUser, error) {
- userEndpoint := common.GetEnvOrDefaultString("LINUX_DO_USER_ENDPOINT", "https://connect.linux.do/api/user")
- logger.LogDebug(ctx, "[OAuth-LinuxDO] GetUserInfo: user_endpoint=%s", userEndpoint)
- req, err := http.NewRequestWithContext(ctx, "GET", userEndpoint, nil)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Authorization", "Bearer "+token.AccessToken)
- req.Header.Set("Accept", "application/json")
- client := http.Client{Timeout: 5 * time.Second}
- res, err := client.Do(req)
- if err != nil {
- logger.LogError(ctx, fmt.Sprintf("[OAuth-LinuxDO] GetUserInfo error: %s", err.Error()))
- return nil, NewOAuthErrorWithRaw(i18n.MsgOAuthConnectFailed, map[string]any{"Provider": "Linux DO"}, err.Error())
- }
- defer res.Body.Close()
- logger.LogDebug(ctx, "[OAuth-LinuxDO] GetUserInfo response status: %d", res.StatusCode)
- var linuxdoUser linuxdoUser
- if err := json.NewDecoder(res.Body).Decode(&linuxdoUser); err != nil {
- logger.LogError(ctx, fmt.Sprintf("[OAuth-LinuxDO] GetUserInfo decode error: %s", err.Error()))
- return nil, err
- }
- if linuxdoUser.Id == 0 {
- logger.LogError(ctx, "[OAuth-LinuxDO] GetUserInfo failed: invalid user id")
- return nil, NewOAuthError(i18n.MsgOAuthUserInfoEmpty, map[string]any{"Provider": "Linux DO"})
- }
- logger.LogDebug(ctx, "[OAuth-LinuxDO] GetUserInfo: id=%d, username=%s, name=%s, trust_level=%d, active=%v, silenced=%v",
- linuxdoUser.Id, linuxdoUser.Username, linuxdoUser.Name, linuxdoUser.TrustLevel, linuxdoUser.Active, linuxdoUser.Silenced)
- // Check trust level
- if linuxdoUser.TrustLevel < common.LinuxDOMinimumTrustLevel {
- logger.LogWarn(ctx, fmt.Sprintf("[OAuth-LinuxDO] GetUserInfo: trust level too low (required=%d, current=%d)",
- common.LinuxDOMinimumTrustLevel, linuxdoUser.TrustLevel))
- return nil, &TrustLevelError{
- Required: common.LinuxDOMinimumTrustLevel,
- Current: linuxdoUser.TrustLevel,
- }
- }
- logger.LogDebug(ctx, "[OAuth-LinuxDO] GetUserInfo success: id=%d, username=%s", linuxdoUser.Id, linuxdoUser.Username)
- return &OAuthUser{
- ProviderUserID: strconv.Itoa(linuxdoUser.Id),
- Username: linuxdoUser.Username,
- DisplayName: linuxdoUser.Name,
- Extra: map[string]any{
- "trust_level": linuxdoUser.TrustLevel,
- "active": linuxdoUser.Active,
- "silenced": linuxdoUser.Silenced,
- },
- }, nil
- }
- func (p *LinuxDOProvider) IsUserIDTaken(providerUserID string) bool {
- return model.IsLinuxDOIdAlreadyTaken(providerUserID)
- }
- func (p *LinuxDOProvider) FillUserByProviderID(user *model.User, providerUserID string) error {
- user.LinuxDOId = providerUserID
- return user.FillUserByLinuxDOId()
- }
- func (p *LinuxDOProvider) SetProviderUserID(user *model.User, providerUserID string) {
- user.LinuxDOId = providerUserID
- }
- func (p *LinuxDOProvider) GetProviderPrefix() string {
- return "linuxdo_"
- }
- // TrustLevelError indicates the user's trust level is too low
- type TrustLevelError struct {
- Required int
- Current int
- }
- func (e *TrustLevelError) Error() string {
- return "trust level too low"
- }
|