feat: publish v1.0.0-rc.36-custom.1
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
This commit is contained in:
commit
bb7968b612
2381 files changed
+551494
No files matched your search
@@ -0,0 +1,202 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/setting/system_setting"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
TelegramIssuer = "https://oauth.telegram.org"
|
||||
TelegramOAuthFlowContextKey = "telegram_oauth_flow"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTelegramOAuthNotConfigured = errors.New("Telegram OAuth is not configured or enabled. Please contact your administrator.")
|
||||
ErrTelegramOAuthConflict = errors.New("The telegram OAuth provider name is reserved. Ask your administrator to rename the conflicting custom provider.")
|
||||
ErrTelegramOAuthFailed = errors.New("Telegram authorization failed. Please try again.")
|
||||
ErrTelegramAccountNotBound = errors.New("This Telegram account is not linked. Sign in using another method and link it first.")
|
||||
)
|
||||
|
||||
// TelegramOAuthFlow keeps the PKCE secret and original client configuration on
|
||||
// the server. Only AuthorizationURL is sent to the browser.
|
||||
type TelegramOAuthFlow struct {
|
||||
CodeVerifier string `json:"code_verifier"`
|
||||
ClientID string `json:"client_id"`
|
||||
RedirectURI string `json:"redirect_uri"`
|
||||
}
|
||||
|
||||
func TelegramConfigurationError() error {
|
||||
if HasCustomProviderConflict("telegram") {
|
||||
return ErrTelegramOAuthConflict
|
||||
}
|
||||
if !common.TelegramOAuthEnabled || !system_setting.GetTelegramSettings().IsConfigured() {
|
||||
return ErrTelegramOAuthNotConfigured
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTelegramOAuthFlow() (*TelegramOAuthFlow, error) {
|
||||
if err := TelegramConfigurationError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
redirectURI := strings.TrimRight(system_setting.ServerAddress, "/") + "/oauth/telegram"
|
||||
parsed, err := url.Parse(redirectURI)
|
||||
if err != nil || parsed.Host == "" || (parsed.Scheme != "https" && parsed.Scheme != "http") {
|
||||
return nil, ErrTelegramOAuthNotConfigured
|
||||
}
|
||||
return &TelegramOAuthFlow{
|
||||
CodeVerifier: oauth2.GenerateVerifier(),
|
||||
ClientID: strings.TrimSpace(system_setting.GetTelegramSettings().ClientID),
|
||||
RedirectURI: redirectURI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (flow *TelegramOAuthFlow) AuthorizationURL(state string) string {
|
||||
values := url.Values{
|
||||
"client_id": {flow.ClientID},
|
||||
"redirect_uri": {flow.RedirectURI},
|
||||
"response_type": {"code"},
|
||||
"scope": {"openid profile"},
|
||||
"state": {state},
|
||||
"code_challenge": {oauth2.S256ChallengeFromVerifier(flow.CodeVerifier)},
|
||||
"code_challenge_method": {"S256"},
|
||||
}
|
||||
return TelegramIssuer + "/auth?" + values.Encode()
|
||||
}
|
||||
|
||||
type TelegramProvider struct {
|
||||
client *http.Client
|
||||
keys oidc.KeySet
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("telegram", NewTelegramProvider(&http.Client{Timeout: 20 * time.Second}))
|
||||
}
|
||||
|
||||
// NewTelegramProvider shares the HTTP client with a long-lived, cached JWKS
|
||||
// verifier. Tests can exercise the real protocol using a local HTTP transport.
|
||||
func NewTelegramProvider(client *http.Client) *TelegramProvider {
|
||||
return &TelegramProvider{
|
||||
client: client,
|
||||
keys: oidc.NewRemoteKeySet(
|
||||
oidc.ClientContext(context.Background(), client),
|
||||
TelegramIssuer+"/.well-known/jwks.json",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *TelegramProvider) GetName() string { return "Telegram" }
|
||||
|
||||
func (p *TelegramProvider) IsEnabled() bool { return TelegramConfigurationError() == nil }
|
||||
|
||||
func (p *TelegramProvider) ExchangeToken(ctx context.Context, code string, c *gin.Context) (*OAuthToken, error) {
|
||||
if err := TelegramConfigurationError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, _ := c.Get(TelegramOAuthFlowContextKey)
|
||||
flow, ok := value.(*TelegramOAuthFlow)
|
||||
settings := system_setting.GetTelegramSettings()
|
||||
if !ok || flow == nil || flow.CodeVerifier == "" || code == "" ||
|
||||
flow.ClientID != strings.TrimSpace(settings.ClientID) ||
|
||||
flow.RedirectURI != strings.TrimRight(system_setting.ServerAddress, "/")+"/oauth/telegram" {
|
||||
return nil, ErrTelegramOAuthFailed
|
||||
}
|
||||
values := url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
"client_id": {flow.ClientID},
|
||||
"redirect_uri": {flow.RedirectURI},
|
||||
"code_verifier": {flow.CodeVerifier},
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, TelegramIssuer+"/token", strings.NewReader(values.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTelegramOAuthFailed, err)
|
||||
}
|
||||
request.SetBasicAuth(flow.ClientID, strings.TrimSpace(settings.ClientSecret))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
response, err := p.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTelegramOAuthFailed, err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: token endpoint status %d", ErrTelegramOAuthFailed, response.StatusCode)
|
||||
}
|
||||
var token OAuthToken
|
||||
if err := common.DecodeJson(io.LimitReader(response.Body, 1<<20), &token); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTelegramOAuthFailed, err)
|
||||
}
|
||||
if token.IDToken == "" {
|
||||
return nil, ErrTelegramOAuthFailed
|
||||
}
|
||||
token.ClientID = flow.ClientID
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func (p *TelegramProvider) GetUserInfo(ctx context.Context, token *OAuthToken) (*OAuthUser, error) {
|
||||
if err := TelegramConfigurationError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token == nil || token.ClientID != strings.TrimSpace(system_setting.GetTelegramSettings().ClientID) {
|
||||
return nil, ErrTelegramOAuthFailed
|
||||
}
|
||||
verifier := oidc.NewVerifier(TelegramIssuer, p.keys, &oidc.Config{
|
||||
ClientID: token.ClientID, SupportedSigningAlgs: []string{oidc.RS256, oidc.ES256},
|
||||
})
|
||||
verified, err := verifier.Verify(ctx, token.IDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTelegramOAuthFailed, err)
|
||||
}
|
||||
var rawClaims json.RawMessage
|
||||
if err := verified.Claims(&rawClaims); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTelegramOAuthFailed, err)
|
||||
}
|
||||
var claims struct {
|
||||
ID json.Number `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"preferred_username"`
|
||||
}
|
||||
if err := common.Unmarshal(rawClaims, &claims); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTelegramOAuthFailed, err)
|
||||
}
|
||||
id, err := strconv.ParseUint(claims.ID.String(), 10, 64)
|
||||
if err != nil || id == 0 || verified.Subject == "" {
|
||||
return nil, ErrTelegramOAuthFailed
|
||||
}
|
||||
return &OAuthUser{
|
||||
ProviderUserID: strconv.FormatUint(id, 10),
|
||||
Username: claims.Username, DisplayName: claims.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *TelegramProvider) IsUserIDTaken(id string) bool { return model.IsTelegramIdAlreadyTaken(id) }
|
||||
|
||||
func (p *TelegramProvider) FillUserByProviderID(user *model.User, id string) error {
|
||||
stored, err := model.GetUserByTelegramID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*user = *stored
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TelegramProvider) SetProviderUserID(user *model.User, id string) { user.TelegramId = id }
|
||||
|
||||
func (p *TelegramProvider) GetProviderPrefix() string { return "telegram_" }
|
||||
|
||||
func (p *TelegramProvider) ProviderUserIDColumn() string { return "telegram_id" }
|
||||
Reference in new issue
Block a user