1
0
mirror of https://github.com/ihabunek/toot.git synced 2025-05-18 00:58:30 -04:00

Add alternate redirect_uri support for broken instances

Pixelfed instances do not handle OOB OAuth correctly meaning you can't
currently login to them using `toot` (e.g. https://github.com/pixelfed/pixelfed/issues/2522 )

This is a fudge to workaround that by allowing you to specify an
alternate `redirect_uri` for broken servers which can take the HTTP
redirect issued by Pixelfed and let you grab the code.

If you don't have a handy HTTP server, you can use `http://localhost`
and your browser (at least Safari and Chrome) will have the code in
the address bar for copying and pasting into `toot`.
This commit is contained in:
rjp 2024-12-08 13:05:41 +00:00
parent 1368a89125
commit 727b3cfad2
5 changed files with 26 additions and 16 deletions

View File

@ -17,6 +17,7 @@ class App(NamedTuple):
base_url: str
client_id: str
client_secret: str
redirect_uri: str
class User(NamedTuple):

View File

@ -77,12 +77,12 @@ def _tag_action(app, user, tag_name, action) -> Response:
return http.post(app, user, url)
def create_app(base_url):
def create_app(base_url, redirect_uri):
url = f"{base_url}/api/v1/apps"
json = {
'client_name': CLIENT_NAME,
'redirect_uris': 'urn:ietf:wg:oauth:2.0:oob',
'redirect_uris': redirect_uri,
'scopes': SCOPES,
'website': CLIENT_WEBSITE,
}
@ -157,7 +157,7 @@ def fetch_app_token(app):
"client_id": app.client_id,
"client_secret": app.client_secret,
"grant_type": "client_credentials",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"redirect_uri": app.redirect_uri,
"scope": "read write"
}
@ -183,7 +183,7 @@ def get_browser_login_url(app: App) -> str:
"""Returns the URL for manual log in via browser"""
return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({
"response_type": "code",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"redirect_uri": app.redirect_uri,
"scope": SCOPES,
"client_id": app.client_id,
}))
@ -197,7 +197,7 @@ def request_access_token(app: App, authorization_code: str):
'client_id': app.client_id,
'client_secret': app.client_secret,
'code': authorization_code,
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'redirect_uri': app.redirect_uri,
}
return http.anon_post(url, data=data, allow_redirects=False).json()

View File

@ -12,22 +12,22 @@ def find_instance(base_url: str) -> Instance:
raise ConsoleError(f"Instance not found at {base_url}")
def register_app(domain: str, base_url: str) -> App:
def register_app(domain: str, base_url: str, redirect_uri: str) -> App:
try:
response = api.create_app(base_url)
response = api.create_app(base_url, redirect_uri)
except ApiError:
raise ConsoleError("Registration failed.")
app = App(domain, base_url, response['client_id'], response['client_secret'])
app = App(domain, base_url, response['client_id'], response['client_secret'], redirect_uri)
config.save_app(app)
return app
def get_or_create_app(base_url: str) -> App:
def get_or_create_app(base_url: str, redirect_uri: str) -> App:
instance = find_instance(base_url)
domain = _get_instance_domain(instance)
return config.load_app(domain) or register_app(domain, base_url)
return config.load_app(domain, redirect_uri) or register_app(domain, base_url, redirect_uri)
def create_user(app: App, access_token: str) -> User:
@ -53,8 +53,8 @@ def login_username_password(app: App, email: str, password: str) -> User:
def login_auth_code(app: App, authorization_code: str) -> User:
try:
response = api.request_access_token(app, authorization_code)
except Exception:
raise ConsoleError("Login failed")
except Exception as e:
raise ConsoleError("Login failed", e)
return create_user(app, response["access_token"])

View File

@ -77,9 +77,11 @@ you need to paste here.""".replace("\n", " ")
@cli.command()
@instance_option
def login(base_url: str):
@click.option("--redirect", "-r", "redirect_uri", help="Redirect URI to use instead of OOB", prompt=True, default="urn:ietf:wg:oauth:2.0:oob")
def login(base_url: str, redirect_uri: str):
"""Log into an instance using your browser (recommended)"""
app = get_or_create_app(base_url)
app = get_or_create_app(base_url, redirect_uri)
# `redirect_uri` is now stored in `app` for future use / saving.
url = api.get_browser_login_url(app)
click.echo(click.wrap_text(LOGIN_EXPLANATION))

View File

@ -87,10 +87,17 @@ def get_user_app(user_id: str):
return extract_user_app(load_config(), user_id)
def load_app(instance: str) -> Optional[App]:
def load_app(instance: str, redirect_uri: str) -> Optional[App]:
config = load_config()
if instance in config['apps']:
return App(**config['apps'][instance])
a = App(**config['apps'][instance])
# Not sure about this bit - if an app was stored without a `redirect_uri`, should
# loading update it to OOB (the previous default) or to the requested `redirect_uri`?
# Stick to OOB for now because presumably if we've saved the app, the login must
# have worked with OOB and there's no need for a `redirect_uri` update. Maybe?
if a.redirect_uri == "":
a.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
return a
def load_user(user_id: str, throw=False):