Skip to content

Commit

Permalink
Add alternate redirect_uri support for broken instances
Browse files Browse the repository at this point in the history
Pixelfed instances do not handle OOB OAuth correctly meaning you can't
currently login to them using `toot` (e.g. pixelfed/pixelfed#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`.
  • Loading branch information
rjp committed Dec 8, 2024
1 parent 1368a89 commit 727b3cf
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 16 deletions.
1 change: 1 addition & 0 deletions toot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class App(NamedTuple):
base_url: str
client_id: str
client_secret: str
redirect_uri: str


class User(NamedTuple):
Expand Down
10 changes: 5 additions & 5 deletions toot/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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"
}

Expand All @@ -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,
}))
Expand All @@ -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()
Expand Down
14 changes: 7 additions & 7 deletions toot/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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"])

Expand Down
6 changes: 4 additions & 2 deletions toot/cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ def login_cli(base_url: str, email: str, password: str):

@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))
Expand Down
11 changes: 9 additions & 2 deletions toot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 727b3cf

Please sign in to comment.