-
Notifications
You must be signed in to change notification settings - Fork 2
Implements multi-user accounts and Refresh Expired Auth Tokens #9
Conversation
This feature commit introduces some major changes. 1. **Account Management**: - Adds `account.clj`, which implements multiuser accounts in a global atom, `accounts` - clarifies the use of the abstraction `session`, which is the return object from ATProto `createSession` from the conflation with martian.core.Martian instance - an account is stored as a keyword of user handle in `accounts`, along with `:session` `:m-spec` and a `:config` which holds base-url and openapi source string - pretty much all functions now take a simple handle as a parameter, from which the relevant information is retrieved or populated via the global `accounts` atom 2. **Refresh Expired Auth Tokens**: - Reason for needing to store ATProto Session, is to hold onto the :refreshJwt, which is distinct from the :accessJwt, to refresh session on expired :accessJwt tokens - stores all refresh token Timers in `accounts` atom `:refresh-futures` (should change name...), maybe `:refresh-timers`, yeah 3. **Auth**: - is currently handled by reading in `./auth.edn` in the project root directory and expects a structure like: ```clojure {:handle1 {:username "handle1" :account-password __password1__ :base-url "https://bsky.social"} :handle2 {:username "handle2" :account-password __password2__ :base-url "https://custom-pds.xyz"}...} ``` - auth is a bit of a WIP... happy for suggestions, recommendations on this 4. **Client changes**: - Since `accounts.clj` holds most of the logic for token-sessions, etc., `client` is now a refactored `call` - also added another functions `response-for` that returns the :body of `call` - if no handle is provided, it will use the `:default-account` in `accounts` - if accessJwt is expired, will automatically call refresh-session! and retry 5. **Other Changes**: - Moved the spec definitions to `.../atproto/specs.core.clj` - could be expanded - adds `jwt.clj` for parsing session token, in order to know when to schedule refresh Resolves: [goshatch#6] [goshatch#8]
Hi @hierophantos, thank you so much for the PR! As a first step, could I please ask you to update the documentation (well, the README file, really), with updated usage? Regarding auth, just as an idea, we could get some inspiration from the various emacs packages that need auth to be stored, and setup an overridable function that would retrieve the auth? It could come from an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @hierophantos , thanks so much for your interest and contribution!
I think I'm missing a little bit of context regarding the motivation of this rewrite. @goshatch and I had collaborated on the original design and I had thought it already satisfied the concerns addressed here (with token refresh handled by PR #8).
In the original code, to work with multiple accounts, you'd just call init
multiple times. Then the caller can manage those session objects however they choose. It's true this results in multiple Martian session objects, but they are fairly lightweight. It also permits having any configuration option be different for different sessions (e.g base url), which could be desirable in some cases. Environment variables aren't required, they are just a fallback in case a value is not provided in the specified configuration (this is the same way libraries like the AWS SDK work).
At a high level, I'm a bit concerned with the level of state that this change introduces to the library. Most of the methods alter some persistent state, meaning that users of the library need to comprehend not just the function signatures but the way the library manages state in general. As much as possible, libraries (even moreso than regular code) should be stateless in Clojure.
I also think it is a mistake to require config to be loaded from a specific file. There are many deployment situations where that is undesirable or impossible, and in general I'd prefer not to have a library make that choice for me... configuration values should be passed in. The client can load them from a file if they want. Even the env var fallback is just a convenience: fundamentally, libraries should be configured by the caller.
I like the change to actually inspect the timeout on the token, although we still need to handle the "fail then reauth" pattern since clock skew is real. Having a global stateful timer for the refresh is unnecessary, however: expiration can be checked prior to each call, keeping the library itself stateless.
Thank you @levand 🙏 Hi @hierophantos! First of all, I want to apologise for the awkward communication on this topic: this is my first time running an open source project with collaborators and I'm still finding my feet. I had a chance to review the code, and I would tend to agree with @levand's points, especially when it comes to letting users of the SDK to set up state of their application the way that works for them, rather than adding state to our library. For the next steps, I would propose the following:
I would welcome your feedback and improvements on both of these, and again, apologies for any awkwardness in handling this. |
One motivating factor was to clarify the abstraction around what is being called
In my rewrite, things work similarly. User calls I've tried to document this throughout the codebase, and even included spec definitions that show this structure, and can be later incorporated in further validation and testing.
For people simply making API calls,
I agree. I call this out in my comments concerning Auth in general. I'm conceptualizing a namespace
Indeed. The changes to |
Thanks to @hierophantos and PR #9 for the inspiration. This commit also includes a base case for the `expired?` function allowing handling unauthenticated user scenarios.
Thanks to @hierophantos and PR #9 for the inspiration. This commit also includes a base case for the `expired?` function allowing handling unauthenticated user scenarios.
Thanks to @hierophantos and PR #9 for the inspiration. This commit also includes a base case for the `expired?` function allowing handling unauthenticated user scenarios.
Thanks to @hierophantos and PR #9 for the inspiration. This commit also includes a base case for the `expired?` function allowing handling unauthenticated user scenarios.
This feature commit introduces some major changes.
Adds
accounts.clj
, which implements multiuser accounts in a global atom,accounts
clarifies the use of the abstraction
session
, which is the return object from ATProtocreateSession
from the conflation with martian.core.Martian instancean account is stored as a keyword of user handle in
accounts
, and a map structure of:session
:m-spec
and a:config
which holds base-url and openapi source stringpretty much all functions now take a simple handle as a parameter, from which the relevant information is retrieved or populated via the global
accounts
atomaccounts
atom:refresh-futures
(should change name...), maybe:refresh-timers
, yeah./auth.edn
in the project root directory and expects a structure like:accounts.clj
holds most of the logic for token-sessions, etc.,client
is now a refactoredcall
response-for
that returns the :body ofcall
:default-account
inaccounts
.../atproto/specs/core.clj
- could be expanded upon in the futurejwt.clj
for parsing session token, in order to know when to schedule refreshResolves: [https://github.com//issues/6] [https://github.com//pull/8]