├── .github └── workflows │ └── pypi.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── auth └── OpenAiAuth.go ├── go.mod ├── go.sum ├── main.go ├── requirements.txt ├── setup.py └── src └── OpenAIAuth.py /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | push: 7 | branches: [main] 8 | pull_request: 9 | types: [opened, reopened, synchronize] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | ci: 16 | if: github.event_name != 'release' 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.9", "3.11"] 22 | os: [ubuntu-latest, windows-latest] 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: make 31 | - name: Build CI 32 | run: make build 33 | - name: Syntax CI 34 | run: make ci 35 | 36 | deploy: 37 | runs-on: ubuntu-latest 38 | if: github.event_name == 'release' 39 | steps: 40 | - uses: actions/checkout@v3 41 | - name: Set up Python 42 | uses: actions/setup-python@v4 43 | with: 44 | python-version: '3.11.0' 45 | - name: Install dependencies 46 | run: make 47 | - name: Build package 48 | run: make build 49 | - name: Publish package 50 | uses: pypa/gh-action-pypi-publish@v1.8.5 51 | with: 52 | user: __token__ 53 | password: ${{ secrets.PYPI_API_TOKEN }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | test.py 131 | 132 | .idea 133 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.flake8Enabled": false, 3 | "python.linting.enabled": true, 4 | "python.linting.pylintEnabled": true, 5 | "cSpell.words": [ 6 | "webauthn" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Antonio Cheong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs 2 | init: 3 | python -m pip install --upgrade pip 4 | python -m pip install -r ./requirements.txt --upgrade 5 | python -m pip install build setuptools wheel flake8 --upgrade 6 | build: 7 | python -m build 8 | ci: 9 | python -m flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 10 | python -m flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics 11 | python setup.py install 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAIAuth 2 | Fetch access tokens for chat.openai.com 3 | 4 | ## Python version 5 | ```py 6 | from OpenAIAuth import Auth0 7 | auth = Auth0(email_address="example@example.com", password="example_password") 8 | access_token = auth.get_access_token() 9 | ``` 10 | 11 | ## Go version 12 | ```go 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | "os" 18 | 19 | "github.com/acheong08/OpenAIAuth/auth" 20 | ) 21 | 22 | func main() { 23 | auth := auth.NewAuthenticator(os.Getenv("OPENAI_EMAIL"), os.Getenv("OPENAI_PASSWORD"), os.Getenv("PROXY")) 24 | err := auth.Begin() 25 | if err.Error != nil { 26 | println("Error: " + err.Details) 27 | println("Location: " + err.Location) 28 | println("Status code: " + fmt.Sprint(err.StatusCode)) 29 | println("Embedded error: " + err.Error.Error()) 30 | return 31 | } 32 | token, err := auth.GetAccessToken() 33 | if err.Error != nil { 34 | println("Error: " + err.Details) 35 | println("Location: " + err.Location) 36 | println("Status code: " + fmt.Sprint(err.StatusCode)) 37 | println("Embedded error: " + err.Error.Error()) 38 | return 39 | } 40 | fmt.Println(token) 41 | } 42 | ``` 43 | 44 | ## Credits 45 | - @linweiyuan 46 | - @rawandahmad698 47 | - @pengzhile 48 | -------------------------------------------------------------------------------- /auth/OpenAiAuth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/url" 9 | "regexp" 10 | "strings" 11 | 12 | http "github.com/bogdanfinn/fhttp" 13 | tls_client "github.com/bogdanfinn/tls-client" 14 | pkce "github.com/nirasan/go-oauth-pkce-code-verifier" 15 | ) 16 | 17 | type Error struct { 18 | Location string 19 | StatusCode int 20 | Details string 21 | Error error 22 | } 23 | 24 | func NewError(location string, statusCode int, details string, err error) *Error { 25 | return &Error{ 26 | Location: location, 27 | StatusCode: statusCode, 28 | Details: details, 29 | Error: err, 30 | } 31 | } 32 | 33 | type Authenticator struct { 34 | EmailAddress string 35 | Password string 36 | Proxy string 37 | Session tls_client.HttpClient 38 | UserAgent string 39 | State string 40 | URL string 41 | Verifier_code string 42 | Verifier_challenge string 43 | AuthResult AuthResult 44 | } 45 | 46 | type AuthResult struct { 47 | AccessToken string `json:"access_token"` 48 | PUID string `json:"puid"` 49 | } 50 | 51 | func NewAuthenticator(emailAddress, password, proxy string) *Authenticator { 52 | auth := &Authenticator{ 53 | EmailAddress: emailAddress, 54 | Password: password, 55 | Proxy: proxy, 56 | UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", 57 | } 58 | jar := tls_client.NewCookieJar() 59 | options := []tls_client.HttpClientOption{ 60 | tls_client.WithTimeoutSeconds(20), 61 | tls_client.WithClientProfile(tls_client.Okhttp4Android13), 62 | tls_client.WithNotFollowRedirects(), 63 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument 64 | // Proxy 65 | tls_client.WithProxyUrl(proxy), 66 | } 67 | auth.Session, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...) 68 | 69 | // PKCE 70 | verifier, _ := pkce.CreateCodeVerifier() 71 | auth.Verifier_code = verifier.String() 72 | auth.Verifier_challenge = verifier.CodeChallengeS256() 73 | 74 | return auth 75 | } 76 | 77 | func (auth *Authenticator) URLEncode(str string) string { 78 | return url.QueryEscape(str) 79 | } 80 | 81 | func (auth *Authenticator) Begin() *Error { 82 | 83 | url := "https://chat.openai.com/api/auth/csrf" 84 | req, err := http.NewRequest("GET", url, nil) 85 | if err != nil { 86 | return NewError("begin", 0, "", err) 87 | } 88 | 89 | req.Header.Set("Host", "chat.openai.com") 90 | req.Header.Set("Accept", "*/*") 91 | req.Header.Set("Connection", "keep-alive") 92 | req.Header.Set("User-Agent", auth.UserAgent) 93 | req.Header.Set("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") 94 | req.Header.Set("Referer", "https://chat.openai.com/auth/login") 95 | req.Header.Set("Accept-Encoding", "gzip, deflate, br") 96 | 97 | resp, err := auth.Session.Do(req) 98 | if err != nil { 99 | return NewError("begin", 0, "", err) 100 | } 101 | defer resp.Body.Close() 102 | 103 | body, err := io.ReadAll(resp.Body) 104 | if err != nil { 105 | return NewError("begin", 0, "", err) 106 | } 107 | 108 | if resp.StatusCode == 200 && strings.Contains(resp.Header.Get("Content-Type"), "json") { 109 | 110 | var csrfTokenResponse struct { 111 | CsrfToken string `json:"csrfToken"` 112 | } 113 | err = json.Unmarshal(body, &csrfTokenResponse) 114 | if err != nil { 115 | return NewError("begin", 0, "", err) 116 | } 117 | 118 | csrfToken := csrfTokenResponse.CsrfToken 119 | return auth.partOne(csrfToken) 120 | } else { 121 | err := NewError("begin", resp.StatusCode, string(body), fmt.Errorf("error: Check details")) 122 | return err 123 | } 124 | } 125 | 126 | func (auth *Authenticator) partOne(csrfToken string) *Error { 127 | 128 | auth_url := "https://chat.openai.com/api/auth/signin/auth0?prompt=login" 129 | headers := map[string]string{ 130 | "Host": "chat.openai.com", 131 | "User-Agent": auth.UserAgent, 132 | "Content-Type": "application/x-www-form-urlencoded", 133 | "Accept": "*/*", 134 | "Sec-Gpc": "1", 135 | "Accept-Language": "en-US,en;q=0.8", 136 | "Origin": "https://chat.openai.com", 137 | "Sec-Fetch-Site": "same-origin", 138 | "Sec-Fetch-Mode": "cors", 139 | "Sec-Fetch-Dest": "empty", 140 | "Referer": "https://chat.openai.com/auth/login", 141 | "Accept-Encoding": "gzip, deflate", 142 | } 143 | 144 | // Construct payload 145 | payload := fmt.Sprintf("callbackUrl=%%2F&csrfToken=%s&json=true", csrfToken) 146 | req, _ := http.NewRequest("POST", auth_url, strings.NewReader(payload)) 147 | 148 | for k, v := range headers { 149 | req.Header.Set(k, v) 150 | } 151 | 152 | resp, err := auth.Session.Do(req) 153 | if err != nil { 154 | return NewError("part_one", 0, "Failed to send request", err) 155 | } 156 | defer resp.Body.Close() 157 | body, err := io.ReadAll(resp.Body) 158 | if err != nil { 159 | return NewError("part_one", 0, "Failed to read body", err) 160 | } 161 | 162 | if resp.StatusCode == 200 && strings.Contains(resp.Header.Get("Content-Type"), "json") { 163 | var urlResponse struct { 164 | URL string `json:"url"` 165 | } 166 | err = json.Unmarshal(body, &urlResponse) 167 | if err != nil { 168 | return NewError("part_one", 0, "Failed to decode JSON", err) 169 | } 170 | if urlResponse.URL == "https://chat.openai.com/api/auth/error?error=OAuthSignin" || strings.Contains(urlResponse.URL, "error") { 171 | err := NewError("part_one", resp.StatusCode, "You have been rate limited. Please try again later.", fmt.Errorf("error: Check details")) 172 | return err 173 | } 174 | return auth.partTwo(urlResponse.URL) 175 | } else { 176 | return NewError("part_one", resp.StatusCode, string(body), fmt.Errorf("error: Check details")) 177 | } 178 | } 179 | 180 | func (auth *Authenticator) partTwo(url string) *Error { 181 | 182 | headers := map[string]string{ 183 | "Host": "auth0.openai.com", 184 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 185 | "Connection": "keep-alive", 186 | "User-Agent": auth.UserAgent, 187 | "Accept-Language": "en-US,en;q=0.9", 188 | } 189 | 190 | req, _ := http.NewRequest("GET", url, nil) 191 | for k, v := range headers { 192 | req.Header.Set(k, v) 193 | } 194 | 195 | resp, err := auth.Session.Do(req) 196 | if err != nil { 197 | return NewError("part_two", 0, "Failed to make request", err) 198 | } 199 | defer resp.Body.Close() 200 | body, _ := io.ReadAll(resp.Body) 201 | 202 | if resp.StatusCode == 302 || resp.StatusCode == 200 { 203 | 204 | stateRegex := regexp.MustCompile(`state=(.*)`) 205 | stateMatch := stateRegex.FindStringSubmatch(string(body)) 206 | if len(stateMatch) < 2 { 207 | return NewError("part_two", 0, "Could not find state in response", fmt.Errorf("error: Check details")) 208 | } 209 | 210 | state := strings.Split(stateMatch[1], `"`)[0] 211 | return auth.partThree(state) 212 | } else { 213 | return NewError("part_two", resp.StatusCode, string(body), fmt.Errorf("error: Check details")) 214 | 215 | } 216 | } 217 | func (auth *Authenticator) partThree(state string) *Error { 218 | 219 | url := fmt.Sprintf("https://auth0.openai.com/u/login/identifier?state=%s", state) 220 | emailURLEncoded := auth.URLEncode(auth.EmailAddress) 221 | 222 | payload := fmt.Sprintf( 223 | "state=%s&username=%s&js-available=false&webauthn-available=true&is-brave=false&webauthn-platform-available=true&action=default", 224 | state, emailURLEncoded, 225 | ) 226 | 227 | headers := map[string]string{ 228 | "Host": "auth0.openai.com", 229 | "Origin": "https://auth0.openai.com", 230 | "Connection": "keep-alive", 231 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 232 | "User-Agent": auth.UserAgent, 233 | "Referer": fmt.Sprintf("https://auth0.openai.com/u/login/identifier?state=%s", state), 234 | "Accept-Language": "en-US,en;q=0.9", 235 | "Content-Type": "application/x-www-form-urlencoded", 236 | } 237 | 238 | req, _ := http.NewRequest("POST", url, strings.NewReader(payload)) 239 | 240 | for k, v := range headers { 241 | req.Header.Set(k, v) 242 | } 243 | 244 | resp, err := auth.Session.Do(req) 245 | if err != nil { 246 | return NewError("part_three", 0, "Failed to send request", err) 247 | } 248 | defer resp.Body.Close() 249 | 250 | if resp.StatusCode == 302 || resp.StatusCode == 200 { 251 | return auth.partFour(state) 252 | } else { 253 | return NewError("part_three", resp.StatusCode, "Your email address is invalid.", fmt.Errorf("error: Check details")) 254 | 255 | } 256 | 257 | } 258 | func (auth *Authenticator) partFour(state string) *Error { 259 | 260 | url := fmt.Sprintf("https://auth0.openai.com/u/login/password?state=%s", state) 261 | emailURLEncoded := auth.URLEncode(auth.EmailAddress) 262 | passwordURLEncoded := auth.URLEncode(auth.Password) 263 | payload := fmt.Sprintf("state=%s&username=%s&password=%s&action=default", state, emailURLEncoded, passwordURLEncoded) 264 | 265 | headers := map[string]string{ 266 | "Host": "auth0.openai.com", 267 | "Origin": "https://auth0.openai.com", 268 | "Connection": "keep-alive", 269 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 270 | "User-Agent": auth.UserAgent, 271 | "Referer": fmt.Sprintf("https://auth0.openai.com/u/login/password?state=%s", state), 272 | "Accept-Language": "en-US,en;q=0.9", 273 | "Content-Type": "application/x-www-form-urlencoded", 274 | } 275 | 276 | req, _ := http.NewRequest("POST", url, strings.NewReader(payload)) 277 | 278 | for k, v := range headers { 279 | req.Header.Set(k, v) 280 | } 281 | 282 | resp, err := auth.Session.Do(req) 283 | if err != nil { 284 | return NewError("part_four", 0, "Failed to send request", err) 285 | } 286 | defer resp.Body.Close() 287 | 288 | if resp.StatusCode == 302 { 289 | redirectURL := resp.Header.Get("Location") 290 | return auth.partFive(state, redirectURL) 291 | } else { 292 | body := bytes.NewBuffer(nil) 293 | body.ReadFrom(resp.Body) 294 | return NewError("part_four", resp.StatusCode, body.String(), fmt.Errorf("error: Check details")) 295 | 296 | } 297 | 298 | } 299 | func (auth *Authenticator) partFive(oldState string, redirectURL string) *Error { 300 | 301 | url := "https://auth0.openai.com" + redirectURL 302 | 303 | headers := map[string]string{ 304 | "Host": "auth0.openai.com", 305 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 306 | "Connection": "keep-alive", 307 | "User-Agent": auth.UserAgent, 308 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 309 | "Referer": fmt.Sprintf("https://auth0.openai.com/u/login/password?state=%s", oldState), 310 | } 311 | 312 | req, _ := http.NewRequest("GET", url, nil) 313 | 314 | for k, v := range headers { 315 | req.Header.Set(k, v) 316 | } 317 | 318 | resp, err := auth.Session.Do(req) 319 | if err != nil { 320 | return NewError("part_five", 0, "Failed to send request", err) 321 | } 322 | defer resp.Body.Close() 323 | 324 | if resp.StatusCode == 302 { 325 | return auth.partSix(resp.Header.Get("Location"), url) 326 | } else { 327 | return NewError("part_five", resp.StatusCode, resp.Status, fmt.Errorf("error: Check details")) 328 | 329 | } 330 | 331 | } 332 | func (auth *Authenticator) partSix(url, redirect_url string) *Error { 333 | req, _ := http.NewRequest("GET", url, nil) 334 | for k, v := range map[string]string{ 335 | "Host": "chat.openai.com", 336 | "Accept": "application/json", 337 | "Connection": "keep-alive", 338 | "User-Agent": auth.UserAgent, 339 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 340 | "Referer": redirect_url, 341 | } { 342 | req.Header.Set(k, v) 343 | } 344 | resp, err := auth.Session.Do(req) 345 | if err != nil { 346 | return NewError("part_six", 0, "Failed to send request", err) 347 | } 348 | defer resp.Body.Close() 349 | if err != nil { 350 | return NewError("part_six", 0, "Response was not JSON", err) 351 | } 352 | if resp.StatusCode != 302 { 353 | return NewError("part_six", resp.StatusCode, url, fmt.Errorf("incorrect response code")) 354 | } 355 | // Check location header 356 | if location := resp.Header.Get("Location"); location != "https://chat.openai.com/" { 357 | return NewError("part_six", resp.StatusCode, location, fmt.Errorf("incorrect redirect")) 358 | } 359 | 360 | url = "https://chat.openai.com/api/auth/session" 361 | 362 | req, _ = http.NewRequest("GET", url, nil) 363 | 364 | // Set user agent 365 | req.Header.Set("User-Agent", auth.UserAgent) 366 | 367 | resp, err = auth.Session.Do(req) 368 | if err != nil { 369 | return NewError("get_access_token", 0, "Failed to send request", err) 370 | } 371 | 372 | if resp.StatusCode != 200 { 373 | return NewError("get_access_token", resp.StatusCode, "Incorrect response code", fmt.Errorf("error: Check details")) 374 | } 375 | var result map[string]interface{} 376 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 377 | return NewError("get_access_token", 0, "", err) 378 | } 379 | 380 | // Check if access token in data 381 | if _, ok := result["accessToken"]; !ok { 382 | result_string := fmt.Sprintf("%v", result) 383 | return NewError("part_six", 0, result_string, fmt.Errorf("missing access token")) 384 | } 385 | auth.AuthResult.AccessToken = result["accessToken"].(string) 386 | 387 | return nil 388 | } 389 | 390 | func (auth *Authenticator) GetAccessToken() string { 391 | return auth.AuthResult.AccessToken 392 | } 393 | 394 | func (auth *Authenticator) GetPUID() (string, *Error) { 395 | // Check if user has access token 396 | if auth.AuthResult.AccessToken == "" { 397 | return "", NewError("get_puid", 0, "Missing access token", fmt.Errorf("error: Check details")) 398 | } 399 | // Make request to https://chat.openai.com/backend-api/models 400 | req, _ := http.NewRequest("GET", "https://chat.openai.com/backend-api/models", nil) 401 | // Add headers 402 | req.Header.Add("Authorization", "Bearer "+auth.AuthResult.AccessToken) 403 | req.Header.Add("User-Agent", auth.UserAgent) 404 | req.Header.Add("Accept", "application/json") 405 | req.Header.Add("Accept-Language", "en-US,en;q=0.9") 406 | req.Header.Add("Referer", "https://chat.openai.com/") 407 | req.Header.Add("Origin", "https://chat.openai.com") 408 | req.Header.Add("Connection", "keep-alive") 409 | 410 | resp, err := auth.Session.Do(req) 411 | if err != nil { 412 | return "", NewError("get_puid", 0, "Failed to make request", err) 413 | } 414 | defer resp.Body.Close() 415 | if resp.StatusCode != 200 { 416 | return "", NewError("get_puid", resp.StatusCode, "Failed to make request", fmt.Errorf("error: Check details")) 417 | } 418 | // Find `_puid` cookie in response 419 | for _, cookie := range resp.Cookies() { 420 | if cookie.Name == "_puid" { 421 | auth.AuthResult.PUID = cookie.Value 422 | return cookie.Value, nil 423 | } 424 | } 425 | // If cookie not found, return error 426 | return "", NewError("get_puid", 0, "PUID cookie not found", fmt.Errorf("error: Check details")) 427 | } 428 | 429 | func (auth *Authenticator) GetAuthResult() AuthResult { 430 | return auth.AuthResult 431 | } 432 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/acheong08/OpenAIAuth 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/bogdanfinn/fhttp v0.5.19 7 | github.com/bogdanfinn/tls-client v1.3.8 8 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.0.4 // indirect 13 | github.com/bogdanfinn/utls v1.5.15 // indirect 14 | github.com/klauspost/compress v1.15.12 // indirect 15 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect 16 | golang.org/x/crypto v0.1.0 // indirect 17 | golang.org/x/net v0.7.0 // indirect 18 | golang.org/x/sys v0.5.0 // indirect 19 | golang.org/x/text v0.7.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 2 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/bogdanfinn/fhttp v0.5.19 h1:/FKuFAtSw3+iZyNkaWXRDSVqMmOvThDjXanlG6/DXos= 4 | github.com/bogdanfinn/fhttp v0.5.19/go.mod h1:emv9FntlC5eAyrIUhCi6oC5NLoBC9d4AJLCq2T1bobY= 5 | github.com/bogdanfinn/tls-client v1.3.8 h1:HIucpArqyqOUIN/7MDJJA9Ngt+lKHUyoQ2y2CUjIDNM= 6 | github.com/bogdanfinn/tls-client v1.3.8/go.mod h1:dMt6v22wNyA1Y4e3Us6jK3lC64Y6FE2cziydZ7pGK3A= 7 | github.com/bogdanfinn/utls v1.5.15 h1:XUUMJZh2AptaouuwUrc/RQOYgyV89rstC5Nj3FSP43s= 8 | github.com/bogdanfinn/utls v1.5.15/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o= 9 | github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= 10 | github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 11 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c h1:4RYnE0ISVwRxm9Dfo7utw1dh0kdRDEmVYq2MFVLy5zI= 12 | github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= 13 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= 14 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= 15 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= 16 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 17 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 18 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 19 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 20 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 21 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 22 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 23 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/acheong08/OpenAIAuth/auth" 9 | ) 10 | 11 | func main() { 12 | auth := auth.NewAuthenticator(os.Getenv("OPENAI_EMAIL"), os.Getenv("OPENAI_PASSWORD"), os.Getenv("PROXY")) 13 | err := auth.Begin() 14 | if err != nil { 15 | println("Error: " + err.Details) 16 | println("Location: " + err.Location) 17 | println("Status code: " + fmt.Sprint(err.StatusCode)) 18 | println("Embedded error: " + err.Error.Error()) 19 | return 20 | } 21 | // if os.Getenv("PROXY") != "" { 22 | _, err = auth.GetPUID() 23 | if err != nil { 24 | println("Error: " + err.Details) 25 | println("Location: " + err.Location) 26 | println("Status code: " + fmt.Sprint(err.StatusCode)) 27 | println("Embedded error: " + err.Error.Error()) 28 | return 29 | } 30 | // } 31 | // JSON encode auth.GetAuthResult() 32 | result := auth.GetAuthResult() 33 | result_json, _ := json.Marshal(result) 34 | println(string(result_json)) 35 | } 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages 2 | from setuptools import setup 3 | 4 | setup( 5 | name="OpenAIAuth", 6 | version="3.0.0", 7 | license="MIT", 8 | author="pengzhile", 9 | author_email="acheong@student.dalat.org", 10 | description="OpenAI Authentication Reverse Engineered", 11 | packages=find_packages("src"), 12 | package_dir={"": "src"}, 13 | py_modules=["OpenAIAuth"], 14 | url="https://github.com/acheong08/OpenAIAuth", 15 | project_urls={"Bug Report": "https://github.com/acheong08/OpenAIAuth/issues/new"}, 16 | install_requires=[ 17 | "tls_client", 18 | ], 19 | classifiers=[ 20 | "License :: OSI Approved :: MIT License", 21 | "Intended Audience :: Developers", 22 | "Natural Language :: English", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | "Programming Language :: Python :: 3 :: Only", 25 | "Programming Language :: Python :: 3.7", 26 | "Programming Language :: Python :: 3.8", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | ], 31 | long_description=open("README.md", "rt", encoding="utf-8").read(), 32 | long_description_content_type="text/markdown", 33 | ) 34 | -------------------------------------------------------------------------------- /src/OpenAIAuth.py: -------------------------------------------------------------------------------- 1 | # Credits to github.com/rawandahmad698/PyChatGPT 2 | import re 3 | import os 4 | import urllib 5 | 6 | import tls_client as requests 7 | 8 | 9 | class Error(Exception): 10 | """ 11 | Base error class 12 | """ 13 | 14 | location: str 15 | status_code: int 16 | details: str 17 | 18 | def __init__(self, location: str, status_code: int, details: str): 19 | self.location = location 20 | self.status_code = status_code 21 | self.details = details 22 | 23 | 24 | class Auth0: 25 | """ 26 | OpenAI Authentication Reverse Engineered 27 | """ 28 | 29 | def __init__( 30 | self, 31 | email_address: str, 32 | password: str, 33 | puid: str = None, 34 | proxy: str = None, 35 | ): 36 | puid = puid or os.environ.get("PUID") 37 | # if not puid: 38 | # raise ValueError("PUID is required") 39 | self.email_address = email_address 40 | self.password = password 41 | self.proxy = proxy 42 | self.session = requests.Session( 43 | client_identifier="chrome112", 44 | random_tls_extension_order=True, 45 | ) 46 | proxies = { 47 | "http": self.proxy, 48 | "https": self.proxy, 49 | } 50 | self.session.proxies.update(proxies) 51 | self.access_token: str = None 52 | self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" 53 | self.session.cookies.set("_puid", puid) 54 | 55 | @staticmethod 56 | def url_encode(string: str) -> str: 57 | """ 58 | URL encode a string 59 | :param string: 60 | :return: 61 | """ 62 | return urllib.parse.quote(string) 63 | 64 | def begin(self) -> None: 65 | """ 66 | In part two, We make a request to https://chat.openai.com/api/auth/csrf and grab a fresh csrf token 67 | """ 68 | url = "https://chat.openai.com/api/auth/csrf" 69 | headers = { 70 | "Host": "chat.openai.com", 71 | "Accept": "*/*", 72 | "Connection": "keep-alive", 73 | "User-Agent": self.user_agent, 74 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 75 | "Referer": "https://chat.openai.com/auth/login", 76 | "Accept-Encoding": "gzip, deflate, br", 77 | } 78 | response = self.session.get( 79 | url=url, 80 | headers=headers, 81 | ) 82 | if response.status_code == 200 and "json" in response.headers["Content-Type"]: 83 | csrf_token = response.json()["csrfToken"] 84 | # self.session.cookies.set("__Host-next-auth.csrf-token", csrf_token) 85 | self.__part_one(token=csrf_token) 86 | else: 87 | error = Error( 88 | location="begin", 89 | status_code=response.status_code, 90 | details=response.text, 91 | ) 92 | print(error.details) 93 | raise error 94 | 95 | def __part_one(self, token: str) -> None: 96 | """ 97 | We reuse the token from part to make a request to /api/auth/signin/auth0?prompt=login 98 | """ 99 | url = "https://chat.openai.com/api/auth/signin/auth0?prompt=login" 100 | payload = f"callbackUrl=%2F&csrfToken={token}&json=true" 101 | headers = { 102 | "Host": "chat.openai.com", 103 | "User-Agent": self.user_agent, 104 | "Content-Type": "application/x-www-form-urlencoded", 105 | "Accept": "*/*", 106 | "Sec-Gpc": "1", 107 | "Accept-Language": "en-US,en;q=0.8", 108 | "Origin": "https://chat.openai.com", 109 | "Sec-Fetch-Site": "same-origin", 110 | "Sec-Fetch-Mode": "cors", 111 | "Sec-Fetch-Dest": "empty", 112 | "Referer": "https://chat.openai.com/auth/login", 113 | "Accept-Encoding": "gzip, deflate", 114 | # 115 | } 116 | response = self.session.post(url=url, headers=headers, data=payload) 117 | if response.status_code == 200 and "json" in response.headers["Content-Type"]: 118 | url = response.json()["url"] 119 | if ( 120 | url == "https://chat.openai.com/api/auth/error?error=OAuthSignin" 121 | or "error" in url 122 | ): 123 | error = Error( 124 | location="__part_one", 125 | status_code=response.status_code, 126 | details="You have been rate limited. Please try again later.", 127 | ) 128 | raise error 129 | self.__part_two(url=url) 130 | else: 131 | error = Error( 132 | location="__part_one", 133 | status_code=response.status_code, 134 | details=response.text, 135 | ) 136 | raise error 137 | 138 | def __part_two(self, url: str) -> None: 139 | """ 140 | We make a GET request to url 141 | :param url: 142 | :return: 143 | """ 144 | headers = { 145 | "Host": "auth0.openai.com", 146 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 147 | "Connection": "keep-alive", 148 | "User-Agent": self.user_agent, 149 | "Accept-Language": "en-US,en;q=0.9", 150 | "Referer": "https://chat.openai.com/", 151 | } 152 | response = self.session.get( 153 | url=url, 154 | headers=headers, 155 | ) 156 | if response.status_code == 302 or response.status_code == 200: 157 | state = re.findall(r"state=(.*)", response.text)[0] 158 | state = state.split('"')[0] 159 | self.__part_three(state=state) 160 | else: 161 | error = Error( 162 | location="__part_two", 163 | status_code=response.status_code, 164 | details=response.text, 165 | ) 166 | raise error 167 | 168 | def __part_three(self, state: str) -> None: 169 | """ 170 | We use the state to get the login page 171 | """ 172 | url = f"https://auth0.openai.com/u/login/identifier?state={state}" 173 | 174 | headers = { 175 | "Host": "auth0.openai.com", 176 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 177 | "Connection": "keep-alive", 178 | "User-Agent": self.user_agent, 179 | "Accept-Language": "en-US,en;q=0.9", 180 | "Referer": "https://chat.openai.com/", 181 | } 182 | response = self.session.get(url, headers=headers) 183 | if response.status_code == 200: 184 | self.__part_four(state=state) 185 | else: 186 | error = Error( 187 | location="__part_three", 188 | status_code=response.status_code, 189 | details=response.text, 190 | ) 191 | raise error 192 | 193 | def __part_four(self, state: str) -> None: 194 | """ 195 | We make a POST request to the login page with the captcha, email 196 | :param state: 197 | :return: 198 | """ 199 | url = f"https://auth0.openai.com/u/login/identifier?state={state}" 200 | email_url_encoded = self.url_encode(self.email_address) 201 | 202 | payload = ( 203 | f"state={state}&username={email_url_encoded}&js-available=false&webauthn-available=true&is" 204 | f"-brave=false&webauthn-platform-available=true&action=default " 205 | ) 206 | 207 | headers = { 208 | "Host": "auth0.openai.com", 209 | "Origin": "https://auth0.openai.com", 210 | "Connection": "keep-alive", 211 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 212 | "User-Agent": self.user_agent, 213 | "Referer": f"https://auth0.openai.com/u/login/identifier?state={state}", 214 | "Accept-Language": "en-US,en;q=0.9", 215 | "Content-Type": "application/x-www-form-urlencoded", 216 | } 217 | response = self.session.post( 218 | url, 219 | headers=headers, 220 | data=payload, 221 | ) 222 | if response.status_code == 302 or response.status_code == 200: 223 | self.__part_five(state=state) 224 | else: 225 | error = Error( 226 | location="__part_four", 227 | status_code=response.status_code, 228 | details="Your email address is invalid.", 229 | ) 230 | raise error 231 | 232 | def __part_five(self, state: str) -> None: 233 | """ 234 | We enter the password 235 | :param state: 236 | :return: 237 | """ 238 | 239 | email_url_encoded = self.url_encode(self.email_address) 240 | password_url_encoded = self.url_encode(self.password) 241 | payload = f"state={state}&username={email_url_encoded}&password={password_url_encoded}&action=default" 242 | url = f"https://auth0.openai.com/u/login/password?state={state}" 243 | headers = { 244 | "Host": "auth0.openai.com", 245 | "Origin": "https://auth0.openai.com", 246 | "Connection": "keep-alive", 247 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 248 | "User-Agent": self.user_agent, 249 | "Referer": f"https://auth0.openai.com/u/login/password?state={state}", 250 | "Accept-Language": "en-US,en;q=0.9", 251 | "Content-Type": "application/x-www-form-urlencoded", 252 | } 253 | response = self.session.post( 254 | url, 255 | headers=headers, 256 | allow_redirects=False, 257 | data=payload, 258 | ) 259 | if response.status_code == 302: 260 | redirect_url = response.headers.get("Location") 261 | self.__part_six(old_state=state, redirect_url=redirect_url) 262 | else: 263 | error = Error( 264 | location="__part_five", 265 | status_code=response.status_code, 266 | details="Your credentials are invalid.", 267 | ) 268 | raise error 269 | 270 | def __part_six(self, old_state: str, redirect_url) -> None: 271 | url = "https://auth0.openai.com" + redirect_url 272 | headers = { 273 | "Host": "auth0.openai.com", 274 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 275 | "Connection": "keep-alive", 276 | "User-Agent": self.user_agent, 277 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 278 | "Referer": f"https://auth0.openai.com/u/login/password?state={old_state}", 279 | } 280 | response = self.session.get(url, headers=headers, allow_redirects=False) 281 | if response.status_code == 302: 282 | redirect_url = response.headers.get("Location") 283 | self.__part_seven(redirect_url=redirect_url, previous_url=url) 284 | else: 285 | error = Error( 286 | location="__part_six", 287 | status_code=response.status_code, 288 | details=response.text, 289 | ) 290 | raise error 291 | 292 | def __part_seven(self, redirect_url: str, previous_url: str) -> None: 293 | url = redirect_url 294 | headers = { 295 | "Host": "chat.openai.com", 296 | "Accept": "application/json", 297 | "Connection": "keep-alive", 298 | "User-Agent": self.user_agent, 299 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", 300 | "Referer": previous_url, 301 | } 302 | response = self.session.get(url, headers=headers, allow_redirects=True) 303 | if response.status_code == 200: 304 | return 305 | else: 306 | error = Error( 307 | location="__part_seven", 308 | status_code=response.status_code, 309 | details=response.text, 310 | ) 311 | raise error 312 | 313 | def get_access_token(self): 314 | """ 315 | Gets access token 316 | """ 317 | self.begin() 318 | response = self.session.get( 319 | "https://chat.openai.com/api/auth/session", 320 | ) 321 | if response.status_code == 200: 322 | self.access_token = response.json().get("accessToken") 323 | return self.access_token 324 | else: 325 | error = Error( 326 | location="get_access_token", 327 | status_code=response.status_code, 328 | details=response.text, 329 | ) 330 | raise error 331 | 332 | def get_puid(self) -> str: 333 | url = os.getenv("OPENAI_MODELS_URL", "https://bypass.churchless.tech/models") 334 | headers = { 335 | "Authorization": "Bearer " + self.access_token, 336 | } 337 | resp = self.session.get(url, headers=headers) 338 | if resp.status_code == 200: 339 | # Get _puid cookie 340 | puid = resp.headers.get("Set-Cookie", "") 341 | if not puid: 342 | raise Exception("Get _puid cookie failed.") 343 | self.puid = puid.split("_puid=")[1].split(";")[0] 344 | return self.puid 345 | else: 346 | raise Exception(resp.text) 347 | 348 | 349 | if __name__ == "__main__": 350 | import os 351 | 352 | email = os.getenv("OPENAI_EMAIL") 353 | password = os.getenv("OPENAI_PASSWORD") 354 | openai = Auth0(email, password) 355 | print(openai.get_access_token()) 356 | 357 | print(openai.get_puid()) 358 | --------------------------------------------------------------------------------