Skip to content
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

client: allow to authenticate with an existing refresh token #804

Merged
merged 2 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/angry-seals-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lens-protocol/client": patch
---

**chore**: Relax node version requirements to >18 <21
5 changes: 5 additions & 0 deletions .changeset/honest-buses-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lens-protocol/client": minor
---

**feat**: Added `authentication.authenticateWith` method to allow to authenticate LensClient with an existing refresh token
5 changes: 0 additions & 5 deletions examples/node/scripts/authentication/authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ import { setupWallet } from '../shared/setupWallet';
async function main() {
const client = new LensClient({
environment: development,
headers: {
origin: 'https://lens-scripts.example',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
},
});

const wallet = setupWallet();
Expand Down
21 changes: 21 additions & 0 deletions examples/node/scripts/authentication/authenticateWith.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { LensClient, development } from '@lens-protocol/client';

async function main() {
const client = new LensClient({
environment: development,
});

const token = 'YOUR_VALID_REFRESH_TOKEN_HERE';
await client.authentication.authenticateWith({ refreshToken: token });

console.log(`Is LensClient authenticated? `, await client.authentication.isAuthenticated());

const accessTokenResult = await client.authentication.getAccessToken();
const accessToken = accessTokenResult.unwrap();
const profileId = await client.authentication.getProfileId();

console.log(`Authenticated profileId: `, profileId);
console.log(`Access token: `, accessToken);
}

main();
36 changes: 36 additions & 0 deletions examples/node/scripts/authentication/overwriteOrigin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { LensClient, development } from '@lens-protocol/client';

import { setupWallet } from '../shared/setupWallet';

async function main() {
const client = new LensClient({
environment: development,
headers: {
origin: 'https://lens-scripts.example',
},
});

const wallet = setupWallet();
const address = await wallet.getAddress();

const managedProfiles = await client.wallet.profilesManaged({ for: wallet.address });

if (managedProfiles.items.length === 0) {
throw new Error(`You don't manage any profiles, create one first`);
}

const { id, text } = await client.authentication.generateChallenge({
signedBy: address,
for: managedProfiles.items[0].id,
});

console.log(`Challenge: `, text); // Notice the origin URL in the challenge message

const signature = await wallet.signMessage(text);

await client.authentication.authenticate({ id, signature });

console.log(`Is LensClient authenticated? `, await client.authentication.isAuthenticated());
}

main();
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"typescript": "5.2.2"
},
"engines": {
"node": "^18.15.0"
"node": ">=18 <21"
},
"prettier": "@lens-protocol/prettier-config",
"babel": {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/LensClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Environment } from './environments';
import {
Explore,
Feed,
Handle,
Invites,
Modules,
Momoka,
Expand All @@ -19,7 +20,6 @@ import {
Transaction,
Wallet,
} from './submodules';
import { Handle } from './submodules/handle';

/**
* LensClient configuration
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/authentication/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export class Authentication implements IAuthentication {
this.credentials = new CredentialsStorage(context.storage, context.environment.name);
}

async authenticateWith({ refreshToken }: { refreshToken: string }): Promise<void> {
const credentials = new Credentials(undefined, refreshToken);
await this.credentials.set(credentials);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cesarenaldi I made it as simple as this.
shall we validate if the input is a valid jwt token and if it is a lens refresh token or leave it out to the consumer? if not valid then the API will fail on the refresh step anyway and it will happen the first time you call any client method. wdyt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a simple approach works for now. I have a question about the design choice.

Why you went for a method compared to a configuration option in the constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first that client.authentication module contains all methods related to authentication, so it's easier to find it here if you are checking what is possible,
2nd, you might have a client instance from another place of your app and only authenticate later when you have a token.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's probably not clear but each form of authentication will overwrite the previous one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see and understand the rationale. The only thing is that the method name might not be intelligible. the "fromSomething" kinda make sense as static factory method (e.g. Car.fromParts(...)) but not sure if works well as instance method of a submodule.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to better ideas 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it to

/**
* Authenticate with an existing, valid refresh token.
*/
authenticateWith({ refreshToken }: { refreshToken: string }): Promise<void>;

}

async generateChallenge(request: ChallengeRequest): Promise<AuthChallengeFragment> {
return this.api.challenge(request);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/authentication/IAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import type {
* @group LensClient Modules
*/
export interface IAuthentication {
/**
* Authenticate with an existing, valid refresh token.
*/
authenticateWith({ refreshToken }: { refreshToken: string }): Promise<void>;

/**
* Generate a challenge string for the wallet to sign.
*
Expand Down
13 changes: 13 additions & 0 deletions packages/client/src/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,12 @@ export type {
LastLoggedInProfileRequest,
LinkHandleToProfileRequest,
UnlinkHandleFromProfileRequest,
HandleToAddressRequest,
HidePublicationRequest,
InviteRequest,
LegacyCollectRequest,
LensTransactionStatusRequest,
ModuleMetadataRequest,
MomokaCommentRequest,
MomokaMirrorRequest,
MomokaPostRequest,
Expand Down Expand Up @@ -217,14 +219,22 @@ export type {
FeeFollowModuleRedeemInput,
FollowModuleInput,
FollowModuleRedeemInput,
FraudReasonInput,
IllegalReasonInput,
MultirecipientFeeCollectModuleInput,
NetworkAddressInput,
NftInput,
OpenActionModuleInput,
ProfileFraudReasonInput,
ProfileReportingReasonInput,
ProfileSpamReasonInput,
PublicationStatsInput,
RecipientDataInput,
ReferenceModuleInput,
ReportingReasonInput,
SensitiveReasonInput,
SimpleCollectOpenActionModuleInput,
SpamReasonInput,
UnknownFollowModuleInput,
UnknownFollowModuleRedeemInput,
UnknownOpenActionActRedeemInput,
Expand All @@ -245,6 +255,7 @@ export type {
ChangeProfileManager,
Exact,
Follow,
FollowStatusBulk,
ImageTransform,
InputMaybe,
Maybe,
Expand Down Expand Up @@ -282,11 +293,13 @@ export {
MarketplaceMetadataAttributeDisplayType,
ModuleType,
MomokaValidatorError,
NftCollectionOwnersOrder,
NftContractType,
NotificationType,
OpenActionCategoryType,
OpenActionModuleType,
PoapTokenLayerType,
PopularNftCollectionsOrder,
ProfileActionHistoryType,
ProfileInterestTypes,
ProfileReportingFraudSubreason,
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/submodules/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './explore';
export * from './feed';
export * from './handle';
export * from './invites';
export * from './modules';
export * from './momoka';
Expand Down
4 changes: 3 additions & 1 deletion packages/client/src/submodules/publication/Publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,12 +879,14 @@ export class Publication {
* Predict the next onchain Publication id for a Profile.
*
* @param request - Request object for the method
* @param request.from - ProfileId of the profile to predict the next onchain publication id for
* @returns Publication Id
*/
async predictNextOnChainPublicationId({
from,
}: {
/**
* Profile Id of the profile to predict the next onchain Publication Id for
*/
from: Scalars['ProfileId']['input'];
}): Promise<Scalars['PublicationId']['output']> {
const result = await this.fetchAll({
Expand Down
2 changes: 1 addition & 1 deletion packages/react-web/src/inbox/useEnhanceConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type UseEnhanceConversationResult = {
/**
* Enhance XMTP conversation with a profile of the conversation's peer
*
* You MUST be authenticated via {@link useLogin} to use this hook.
* You MUST be authenticated via `useLogin` to use this hook.
*
* @example
* ```tsx
Expand Down
2 changes: 1 addition & 1 deletion packages/react-web/src/inbox/useEnhanceConversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type UseEnhanceConversationsResult = Prettify<
* Enhance XMTP conversations with profiles of the conversations' peers,
* if conversation is between two Lens profiles.
*
* You MUST be authenticated via {@link useLogin} to use this hook.
* You MUST be authenticated via `useLogin` to use this hook.
*
* @example
* ```tsx
Expand Down
2 changes: 1 addition & 1 deletion packages/react-web/src/inbox/useStartLensConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type UseStartLensConversationResult = ReturnType<typeof useStartConversat
/**
* Start a new XMTP conversation between two Lens profiles.
*
* You MUST be authenticated via {@link useLogin} to use this hook.
* You MUST be authenticated via `useLogin` to use this hook.
*
* @example
* ```tsx
Expand Down
4 changes: 2 additions & 2 deletions packages/react-web/src/inbox/useXmtpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ const defaultOptions: InitXmtpClientOptions = {
};

/**
* Initialize XMTP client using the same Signer as the one provided with {@link LensConfig}.
* Initialize XMTP client using the same Signer as the one provided with `LensConfig`.
* Store XMTP user's decryption key in storage to improve UX.
* Be aware that XMTP user's key must be stored safely.
*
* You MUST be authenticated via {@link useLogin} to use this hook.
* You MUST be authenticated via `useLogin` to use this hook.
*
* @example
* ```tsx
Expand Down
5 changes: 4 additions & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ export {
UserRejectedError,
WalletConnectionError,
} from '@lens-protocol/domain/entities';
export { BroadcastingError } from '@lens-protocol/domain/use-cases/transactions';
export {
BroadcastingError,
BroadcastingErrorReason,
} from '@lens-protocol/domain/use-cases/transactions';
export { NotFoundError } from './NotFoundError';
export {
InsufficientAllowanceError,
Expand Down
Loading