Spaces:
Runtime error
Runtime error
| 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" | |
| } | |