-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RN multiple sessions #520
RN multiple sessions #520
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
c1fc276
to
6bf5ed0
Compare
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.
Couple comments, HUGE improvement adding multiple sessions!!
}, | ||
"peerDependencies": { | ||
"@types/react": "^18.2.75", | ||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0", |
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.
This is a wide range of versions for some of these dependencies, can be bit more explicit about which versions will be used, or is there some reason this range is needed?
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.
I just did this to follow what we do in sdk-react
. I believe because we only use react for hooks, any version above 16.8.0 is sufficient. But we don't want users to think we restrict other major releases so thats why we do this. I could be wrong though - maybe @moe-dev could comment on this. If this is unnecessary I can update both sdk-react
and sdk-react-native
@@ -0,0 +1,275 @@ | |||
import * as Keychain from "react-native-keychain"; | |||
import { | |||
TURNKEY_EMBEDDED_KEY_STORAGE, |
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.
If I understand the flow correctly, we store a long lived api key at TURNKEY_EMBEDDED_KEY_STORAGE in the Keychain, store session keys in the Keychain in their respective "key" as well as in the key index and then store the selected session in the Keychain as well? It feels like the session index may be redundant and could introduce issues with synchronization? But maybe is there a specific reason we need the index that im missing?
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.
the flow looks something like:
- createEmbeddedKey() - this generates an api key-pair and stores the private key in local storage under TURNKEY_EMBEDDED_KEY_STORAGE, and returns the public key
- user uses the public key (targetPublicKey) to make a request to turnkey
- you call createSession() with the credential bundle - this will pull the private key from TURNKEY_EMBEDDED_KEY_STORAGE (also deletes it), decrypt the bundle using the private key, and then saves the session under TURNKEY_DEFAULT_SESSION_STORAGE
so why do we currently store the selected session (TURNKEY_SELECTED_SESSION) and the session index (TURNKEY_SESSION_IDS_INDEX)?
for the useEffect (which runs on app initialization), basically if the user refreshes the app we would like to automatically select the session that was previously selected. Then for the reason we have the TURNKEY_SESSION_IDS_INDEX is because we don't know all the ids we store these sessions under (since the user can set it) so we need to know what service names to pull from in secure storage in the useEffect on app initialization.
I do see what you mean though - let me look into possible ways we can clean this up. Maybe there is a way to get all the service names of stuff in secure storage which would then allow us to remove TURNKEY_SESSION_IDS_INDEX.
|
||
/** | ||
* Retrieves the stored embedded key from secure storage. | ||
* Optionally deletes the key from storage after retrieval. |
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.
just to explain this in writing, the key can be deleted from storage after retrieval because ultimately, the full on decrypted API key will be stored in secure storage, correct? in which case the credential bundle/embedded key are no longer needed?
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.
correct
|
||
- `turnkey-embedded-key`: Stores the private key that corresponds to the public key used when initiating the session request to Turnkey. | ||
- `turnkey-session`: Stores the session credentials, including the private key, public key, and expiry time, which are decrypted from the credential bundle after a session is created. | ||
Using multiple sessions can be beneficial when enabling different authentication methods for various operations. For example, you might authenticate a user with OTP for login while using a passkey-based session for signing transactions. |
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.
🏅
}); | ||
return new TurnkeyClient({ baseUrl: apiBaseUrl }, stamper); | ||
}; | ||
config.onSessionSelected?.(selectedSession!); |
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.
nice!
...(email?.trim() && { userEmail: email }), | ||
}; | ||
|
||
const result = await client.updateUser({ |
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.
realized we're using the raw http Turnkey client here, instead of something akin to @turnkey/sdk-server
which abstracts away the need to specify type/timestamp/org ID
. this is def fine for now, but down the line I wonder if it'd help if we had our React Native SDK also have some similar abstractions
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.
noted
|
||
await clearSelectedSessionKey(); | ||
|
||
setSession(undefined); |
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.
using undefined
here is helpful — there could also be an argument made for just calling setSession()
, without any arguments, but I think this works great too
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.
setSession() is a state function - I don't think we can do that unless we write a helper function and call it inside. I think I would prefer setSession(undefined) but if you disagree I can change it
|
||
if (credentials) { | ||
try { | ||
keys = JSON.parse(credentials.password); |
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.
probably overkill, but wouldn't be too bad to include a description on how Keychain.getGenericPassword()
works. i.e. that we're really actually storing the sensitive data (session keys) under credentials.password
, and that credentials.password
doesn't actually have to be a literal password
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.
done
Summary & Motivation
This PR adds support for multiple sessions by allowing sessions to be created with a custom sessionKey instead of using a single default session. We also introduced the concept of a "selected session," meaning users can switch between sessions by calling
setSelectedSession( {sessionKey: <key here>})
, which updates the client, user, and session info accordingly. Any function calls, like updateUser or createWallet, now apply to the selected session.How I Tested These Changes
tested in a custom app using the expo-template :)
Did you add a changeset?
If updating one of our packages, you'll likely need to add a changeset to your PR. To do so, run
pnpm changeset
.pnpm changeset
will generate a file where you should write a human friendly message about the changes. Note how this (example) includes the package name (should be auto added by the command) along with the type of semver change (major.minor.patch) (which you should set).These changes will be used at release time to determine what packages to publish and how to bump their version. For more context see this comment.