kopia lustrzana https://github.com/ihabunek/toot
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`.pull/512/head
rodzic
1368a89125
commit
727b3cfad2
|
@ -17,6 +17,7 @@ class App(NamedTuple):
|
||||||
base_url: str
|
base_url: str
|
||||||
client_id: str
|
client_id: str
|
||||||
client_secret: str
|
client_secret: str
|
||||||
|
redirect_uri: str
|
||||||
|
|
||||||
|
|
||||||
class User(NamedTuple):
|
class User(NamedTuple):
|
||||||
|
|
10
toot/api.py
10
toot/api.py
|
@ -77,12 +77,12 @@ def _tag_action(app, user, tag_name, action) -> Response:
|
||||||
return http.post(app, user, url)
|
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"
|
url = f"{base_url}/api/v1/apps"
|
||||||
|
|
||||||
json = {
|
json = {
|
||||||
'client_name': CLIENT_NAME,
|
'client_name': CLIENT_NAME,
|
||||||
'redirect_uris': 'urn:ietf:wg:oauth:2.0:oob',
|
'redirect_uris': redirect_uri,
|
||||||
'scopes': SCOPES,
|
'scopes': SCOPES,
|
||||||
'website': CLIENT_WEBSITE,
|
'website': CLIENT_WEBSITE,
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ def fetch_app_token(app):
|
||||||
"client_id": app.client_id,
|
"client_id": app.client_id,
|
||||||
"client_secret": app.client_secret,
|
"client_secret": app.client_secret,
|
||||||
"grant_type": "client_credentials",
|
"grant_type": "client_credentials",
|
||||||
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
"redirect_uri": app.redirect_uri,
|
||||||
"scope": "read write"
|
"scope": "read write"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ def get_browser_login_url(app: App) -> str:
|
||||||
"""Returns the URL for manual log in via browser"""
|
"""Returns the URL for manual log in via browser"""
|
||||||
return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({
|
return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
"redirect_uri": app.redirect_uri,
|
||||||
"scope": SCOPES,
|
"scope": SCOPES,
|
||||||
"client_id": app.client_id,
|
"client_id": app.client_id,
|
||||||
}))
|
}))
|
||||||
|
@ -197,7 +197,7 @@ def request_access_token(app: App, authorization_code: str):
|
||||||
'client_id': app.client_id,
|
'client_id': app.client_id,
|
||||||
'client_secret': app.client_secret,
|
'client_secret': app.client_secret,
|
||||||
'code': authorization_code,
|
'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()
|
return http.anon_post(url, data=data, allow_redirects=False).json()
|
||||||
|
|
14
toot/auth.py
14
toot/auth.py
|
@ -12,22 +12,22 @@ def find_instance(base_url: str) -> Instance:
|
||||||
raise ConsoleError(f"Instance not found at {base_url}")
|
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:
|
try:
|
||||||
response = api.create_app(base_url)
|
response = api.create_app(base_url, redirect_uri)
|
||||||
except ApiError:
|
except ApiError:
|
||||||
raise ConsoleError("Registration failed.")
|
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)
|
config.save_app(app)
|
||||||
|
|
||||||
return 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)
|
instance = find_instance(base_url)
|
||||||
domain = _get_instance_domain(instance)
|
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:
|
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:
|
def login_auth_code(app: App, authorization_code: str) -> User:
|
||||||
try:
|
try:
|
||||||
response = api.request_access_token(app, authorization_code)
|
response = api.request_access_token(app, authorization_code)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
raise ConsoleError("Login failed")
|
raise ConsoleError("Login failed", e)
|
||||||
|
|
||||||
return create_user(app, response["access_token"])
|
return create_user(app, response["access_token"])
|
||||||
|
|
||||||
|
|
|
@ -77,9 +77,11 @@ you need to paste here.""".replace("\n", " ")
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@instance_option
|
@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)"""
|
"""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)
|
url = api.get_browser_login_url(app)
|
||||||
|
|
||||||
click.echo(click.wrap_text(LOGIN_EXPLANATION))
|
click.echo(click.wrap_text(LOGIN_EXPLANATION))
|
||||||
|
|
|
@ -87,10 +87,17 @@ def get_user_app(user_id: str):
|
||||||
return extract_user_app(load_config(), user_id)
|
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()
|
config = load_config()
|
||||||
if instance in config['apps']:
|
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):
|
def load_user(user_id: str, throw=False):
|
||||||
|
|
Ładowanie…
Reference in New Issue