plaidweb / authl Goto Github PK
View Code? Open in Web Editor NEWA library for managing federated identity
License: MIT License
A library for managing federated identity
License: MIT License
Twitter is popular for federated identity.
The EmailHandler should keep track of the addresses it's sent a link to in the last few minutes, and not allow spamming an address. This is both to prevent Authl from being used as an intermediary for flooding someone else's email address, and to avoid issues with a /_login/[email protected]
address living in someone's history causing the user to get spammed (due to browser prefetch or whatever)
The various protocols such as IndieAuth and AutoAuth intend for authorization bearing to be handled by an auth header, rather than a cookie jar. In the Flask configuration especially this seems like a thing that should be abided by.
The basic protocol is described in RFC 7235 and the IndieAuth/AutoAuth usage is discussed in the AutoAuth spec.
This is probably something that needs to be handled on a per-application basis but implementing it in the tests and documenting it in a central place will be helpful for others.
I am also probably severely misinterpreting something, because as far as I can tell the IndieAuth flow doesn't say anything about how authentication is stored between the site and the end user, and all of the IndieAuth-enabled things I'm finding seem to use a cookie jar for storing the actual session. So I'm not clear on how the AutoAuth flow is supposed to work in the first place.
add mypy to the build system
Via @snarfed, attempting to log on via @[email protected]
generates an exception:
Traceback (most recent call last):
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/models.py", line 379, in prepare_url
scheme, auth, host, port, path, query, fragment = parse_url(url)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/urllib3/util/url.py", line 234, in parse_url
raise LocationParseError(url)
urllib3.exceptions.LocationParseError: Failed to parse: //@[email protected]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
response = self.handle_exception(e)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/fluffy/projects/Authl/authl/flask.py", line 179, in login
handler, hid, id_url = instance.get_handler_for_url(me_url)
File "/Users/fluffy/projects/Authl/authl/__init__.py", line 37, in get_handler_for_url
request = request_url(url)
File "/Users/fluffy/projects/Authl/authl/__init__.py", line 63, in request_url
return requests.get(url)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/api.py", line 75, in get
return request('get', url, params=params, **kwargs)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/api.py", line 60, in request
return session.request(method=method, url=url, **kwargs)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/sessions.py", line 519, in request
prep = self.prepare_request(req)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/sessions.py", line 462, in prepare_request
hooks=merge_hooks(request.hooks, self.hooks),
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/models.py", line 313, in prepare
self.prepare_url(url, params)
File "/Users/fluffy/.local/share/virtualenvs/Authl-bMFs1yld/lib/python3.7/site-packages/requests/models.py", line 381, in prepare_url
raise InvalidURL(*e.args)
requests.exceptions.InvalidURL: Failed to parse: //@[email protected]
Issue appears to be that the "this isn't a valid URL" exception handler needs to also catch requests.exceptions.InvalidURL
.
Each handler should at the very least mock out enough of the service to test the success and failure paths
Because why not.
After retrieving the IndieAuth page to get the endpoints, parse the fetched response for name etc.
Right now the Mastodon handler just trusts whatever profile uri the server gives back. There should be a validation step that ensures that the uri is at least one under the instance’s control, to prevent a trivial attack.
Technically an OAuth-based identity does not need a full user URL, it only requires providing the common domain name of the service (E.g. Mastodon.social, twitter.com, etc). It might be nice to signal that in some way, or to even make the standard UX just forward to the authority if the user clicks on the appropriate link for the service in the list (like linking to /login?me=http://twitter.com
or whatever).
But it might be nice to just pretend that the full URL is important for the sake of consistency anyway. (But it might look like a bug if someone has two identities on a remote service and they want to specify one but the handler gives them another...)
Per indieweb/indieauth#36, IndieAuth profiles (and probably other profiles) need a better way to handle URL redirections than simply accepting the final URL. Currently we just chase all redirections and use the final response URL as the canonical URL, but instead we should keep track of redirections and use the last URL that came before the first temporary redirect.
This is the edge-casiest of edge cases and only really applies to IndieAuth/IndieLogin (which are the only handlers which even retrieve the profile page) but it would still be helpful to do the redirection chase. See below for an example of what (again, very rare) edge case would fail.
For that matter, if the canonical profile URL is different, the URL-based detection logic could be rerun so that e.g. https://beesbuzz.biz/twitter
will be treated as https://twitter.com/fluffy
and not dropped as an unhandled auth type.
Provided profile URL | Redirection chain | me URL |
Pass/fail |
---|---|---|---|
http://alice.example.com |
permanent -> https://alice.example.com |
https://alice.example.com |
pass |
http://alice.example.com |
temporary -> https://alice.example.com |
https://alice.example.com |
fail? (different scheme) |
http://alice.example.com |
temporary -> https://alice.example.com |
http://alice.example.com |
pass |
https://alice.example.com |
permanent -> https://example.com/~alice |
https://alice.example.com |
fail (different domain) |
https://alice.example.com |
temporary -> https://example.com/~alice |
https://alice.example.com |
pass |
https://alice.example.com |
permanent -> https://example.com/~alice |
https://example.com/~alice/ |
pass |
While the IndieLogin handler is useful for things like RelMeAuth and the like, it'd also be nice to support IndieAuth directly, since that buys things like AutoAuth and also means that sites won't need to each register with IndieLogin.com (or another broker) to support IndieAuth.
For folks using asgi it'd be nice to be nice
If a WebFinger profile provides multiple profile links, iterate through all of them until one href
provides a supported identity type.
This will require some rearchitecting; currently webfinger detection is a quick-and-dirty hack.
On cloud-based deployments like Heroku, and on load balancers (I repeat myself), there's not currently any way for state to be preserved across process boundaries; all of the state is stored in an ExpiringDict
.
It would be better to go back to storing the state
value in an itsdangerous
-signed token, which was the original design and is generally safe (modulo the concern of replay attacks, which is mitigated by making the signature expire anyway).
Alternately, allow for a persistent backing store for the state tokens, but that's got a lot of other implications to worry about and should only be a last resort.
To prevent a site from being used in an amplification attack or part of an email bomb or whatever, the login endpoint should throttle requests made based on both the me
parameter and on the originating IP address (as determined by eg flask.request.headers.get(“x-forwarded-for”,flask.request.remote_addr)
or whatever the correct invocation is). The timeout should probably be stored in an expiringdict with the next timeout computed by adding the delta between the current timeout and the current time multiplied by some constant, with the initial timeout and constant being configurable.
Care should be taken to not accidentally make this a vector for maliciously locking people out, though.
Instead of using a Pipfile
/Pipfile.lock
for local dev environment it'd be better for the Makefile to set up a local virtualenv for running the tools from, or something.
In the long run maybe we should switch to poetry
instead of setuptools
but that has its own set of issues that seem tangential to what we care about with Authl.
Provide a token storage interface in the config API (dict-like interface). Default to in-process LRUDict but provide some sample implementations:
While we're at it, the existing token_store
thing should probably be renamed to make it less confusing (especially since it isn't a store anymore)
When the IndieAuth profile is fetched it should also stash the various other indieweb endpoint links somewhere, possibly in profile['links']
or something.
Tumblr is still a thing, would be nice to allow logins with that.
TBD if it's actually reasonable to do though, because of the capricious nature of their profile URLs. According to the API docs, there is a permanent UID (which they incorrectly call a uuid) so the Twitter userID-in-fragment hack (e.g. username.tumblr.com#uid
) could work.
There is an official client which should make this a lot easier, and looks very similar in usage to the Twitter client.
If Authl is configured with tester_path
, enable a function in the default template that will make use of it to ask the tester what kind of link it is, if any.
Should be straightforward to implement, and very useful for mobile app services.
Now that the handler discovery supports webfinger directly there's no reason for Mastodon to do its silly thing. Especially since there's other things that use webfinger.
GET /login/[email protected]
is convenient but since it sort of has a side effect it should technically be a POST instead.
In case we really want to support Ubuntu Launchpad and Livejournal. And Dreamwidth except they're talking about adding IndieAuth support anyway.
Instead of just rendering the notification directly, it should stash the notification message somewhere and then forward to a new URL that renders the stashed message. This is possibly a more general solution for part of issue #18.
Maybe for major silo providers that don’t need an identity there could be NASCAR buttons that go straight to the appropriate login endpoint. Like
class Twitter(Handler):
@property
def iconic(self):
return “<img src= https://whatever/twitter.png>”, “https://twitter.com”
And then the flask template could be like
<form method=post>
{%for icon,me in auth.handlers%}
{%if icon %}<button type=submit name=me value=“{me}”>{{icon|safe}}</button>{%endif%}
{%endfor%}
</form>
The mastodon.py package likely does everything we need and would be less code to maintain and test on our end. It’s easier to mock out a library than a series of HTTP transactions.
Right now the callback URL just uses its position in the handler list, which isn't compatible with some OAuth providers (notably Twitter) which require registering a stable callback URL. It also means that if configuration changes while someone is logging in, they will have an odd result occur.
It would be better if the handler gets a stable name that's associated with it.
Needs to actually complete the verification flow, and also test the profile parsing
If the user is already logged in with the same identity as the me
argument, just redirect to the redir
target. That way someone can set a bookmark of, say, https://example.com/login/blog?me=http://fred.example.com
and just keep the same login if they're already identified, or be redirected to their own login page if not.
If there's an error logging in, the disposition.Error
should carry the failed identity URL so that the login form can present it as a default value.
Also, on the Flask handler, the error disposition could be passed through to the template, instead of using the message flashing mechanism which is a bit unwieldy.
If someone enters an invalid URI, an individual detector can die with InvalidSchema
.
If an error occurs during authentication, the login redirection path is lost.
Reproduction: in test.py
, attempt to log on from localhost:5000/some-path
via Mastodon/Twitter/etc., and deny the login. Result: a successful login goes to localhost:5000/
instead.
Add a callback-related endpoint _cb/check/<url>
, which checks the URL and returns the pertinent info from Authl.get_handler_for_url(url)
, providing the name and URL schema in a JSON blob like:
{
'name': 'Email',
'schema': '%',
'placeholder': '[email protected]'
}
or the like.
If no matching endpoint exists, it should return null
or undefined
or false
or something.
Right now token_store is always just an itsdangerous signer, but really it should be an interface with set(value,redir)
get
and pop
, and the default should be an implementation that is backed by the signer. get
and pop
should return a tuple of (value,redir)
. signer-backed impl uses utils.unpack_token
as its implementation.
collected notes for Mastodon handler:
we can use Mastodon.py for this. basic process appears to be:
client_id, client_secret = Mastodon.create_app(
'authl-beesbuzz.biz',
scopes=('read'),
api_base_url='https://instance.name',
redirect_uris=[callback_uri])
client = Mastodon(
client_id=client_id, client_secret=client_secret,
api_base_url='https://instance.name')
return disposition.Redirect(
client.auth_request_url(redirect_uris=callback_uri,scopes=('read')))
and then the callback handler will somehow get a client token that can then be used to somehow look up the user ID.
For some reason, people still use Facebook.
The IndieAuth handler erroneously accepts whatever me
value comes in from the verification response. This is a major security concern, as anyone could set up a rogue authorization_endpoint
which provides any arbitrary me
value, allowing someone to log in as anyone else.
This is a high-priority vulnerability in versions of Authl <= 0.3.0.
The Internet doesn't all speak English.
If the JS code can be structured such that there's no top-level async functions, and old browsers (e.g. IE11) simply elide the attempt to check the remote URL, older browsers could at least have the enable/disable flag working on the button. Which is a slight, if trivial, improvement.
Come up with a set of profile fields to make use of and map into a common format for display/recording purposes
Possible things to track:
On the Flask wrapper, only the identity URL should be stored in the session by default; everything else should be provided via the on_verified
callback so that it's up to the app to store it reasonably (for example, in a UserProfile table or something).
There should be a central store for request tokens and other things (Mastodon client tokens, email request throttles, etc), which can then be offloaded into a shared/persisted data store (memcached/redis/Postgres/whatever) to allow for load balancing and the like.
Provide a better login template for the default login page, possibly with some customization options (such as being able to provide a stylesheet or additional markup or whatever).
Ideally it will include support for the friendly callback discovery/listing functionality that was mocked up in the UI wireframes (and provided by #3).
If an instance is down the lru_cache
decorators around the instance check and client builder will prevent it from ever working again, until the app restarts or whatever. It would be good to use a cache with a timeout instead, and also to raise
instead of return None
as appropriate to avoid transitory results from being cached in the first place.
Short-term fix could just be to remove the caching because really who cares about a few extra requests.
User error report from @SpudRat3
error 500 Exception occurred
request time 2020-02-27 14:18:57-08:00
url https://beesbuzz.biz/_cb/e?t=WyJyYXlpaWlAd29ya3Nob3AzZC5jb20iLCIvNTMzNyJd.XlhAGQ.MaBvCkXF2EBLhIGP97C4_Gr7Xt8
path /_cb/e
full_path /_cb/e?t=WyJyYXlpaWlAd29ya3Nob3AzZC5jb20iLCIvNTMzNyJd.XlhAGQ.MaBvCkXF2EBLhIGP97C4_Gr7Xt8
endpoint authl.callback
url_rule /_cb/<hid>
Exception information KeyError: '_authl.prefill'
User reported second attempt worked
Docs are good.
None of the handlers use that argument and I can't see any way that it would even be used anyway. I think it was a leftover from a time when the handlers were going to form their own callback URL path, rather than leaving that up to the fronting application/framework wrapper.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.