diff --git a/examples/node/scripts/authenticate.ts b/examples/node/scripts/authenticate.ts index 54724fd154..12bd5ca44f 100644 --- a/examples/node/scripts/authenticate.ts +++ b/examples/node/scripts/authenticate.ts @@ -28,7 +28,11 @@ async function main() { const accessTokenResult = await client.authentication.getAccessToken(); const accessToken = accessTokenResult.unwrap(); + const profileIdResult = await client.authentication.getProfileId(); + const profileId = profileIdResult.unwrap(); + console.log(`Is LensClient authenticated? `, await client.authentication.isAuthenticated()); + console.log(`Authenticated profileId: `, profileId); console.log(`Access token: `, accessToken); console.log(`Is access token valid? `, await client.authentication.verify(accessToken)); } diff --git a/examples/node/scripts/profile/changeProfileManagers.ts b/examples/node/scripts/profile/changeProfileManagers.ts index 6a3ca8a578..c9a9b0d831 100644 --- a/examples/node/scripts/profile/changeProfileManagers.ts +++ b/examples/node/scripts/profile/changeProfileManagers.ts @@ -5,9 +5,12 @@ import { setupWallet } from '../shared/setupWallet'; async function main() { const wallet = setupWallet(); - const lensClient = await getAuthenticatedClientFromEthersWallet(wallet); + const client = await getAuthenticatedClientFromEthersWallet(wallet); - const typedDataResult = await lensClient.profile.createChangeProfileManagersTypedData({ + const profileIdResult = await client.authentication.getProfileId(); + const profileId = profileIdResult.unwrap(); + + const typedDataResult = await client.profile.createChangeProfileManagersTypedData({ approveLensManager: true, // changeManagers: [ // { @@ -27,7 +30,7 @@ async function main() { ); // broadcast onchain - const broadcastOnchainResult = await lensClient.transaction.broadcastOnchain({ + const broadcastOnchainResult = await client.transaction.broadcastOnchain({ id, signature: signedTypedData, }); @@ -40,7 +43,8 @@ async function main() { } console.log( - `Successfully changed profile managers with transaction with id ${onchainRelayResult.txId}, txHash: ${onchainRelayResult.txHash}`, + `Successfully changed profile manager for profile ${profileId} with: `, + onchainRelayResult, ); } diff --git a/packages/client/src/authentication/Authentication.ts b/packages/client/src/authentication/Authentication.ts index aae949ce35..8454cc0179 100644 --- a/packages/client/src/authentication/Authentication.ts +++ b/packages/client/src/authentication/Authentication.ts @@ -83,6 +83,21 @@ export class Authentication implements IAuthentication { return failure(new CredentialsExpiredError()); } + async getProfileId(): PromiseResult { + const credentials = await this.storage.get(); + + if (!credentials) { + return failure(new NotAuthenticatedError()); + } + + if (!credentials.canRefresh()) { + return failure(new CredentialsExpiredError()); + } + + return success(credentials.getProfileId()); + } + + // private methods async getRequestHeader(): PromiseResult< Record, CredentialsExpiredError | NotAuthenticatedError diff --git a/packages/client/src/authentication/IAuthentication.ts b/packages/client/src/authentication/IAuthentication.ts index f0afe5453c..dcad3f570f 100644 --- a/packages/client/src/authentication/IAuthentication.ts +++ b/packages/client/src/authentication/IAuthentication.ts @@ -47,4 +47,11 @@ export interface IAuthentication { * @returns The access token */ getAccessToken(): PromiseResult; + + /** + * Get the profileId of authenticated profile. + * + * @returns The access token + */ + getProfileId(): PromiseResult; } diff --git a/packages/client/src/authentication/adapters/Credentials.spec.ts b/packages/client/src/authentication/adapters/Credentials.spec.ts index fd948ba5a1..dc96fba181 100644 --- a/packages/client/src/authentication/adapters/Credentials.spec.ts +++ b/packages/client/src/authentication/adapters/Credentials.spec.ts @@ -3,17 +3,25 @@ import { DateUtils } from '@lens-protocol/shared-kernel'; import { Credentials } from './Credentials'; const accessToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YjE5QzI4OTBjZjk0N0FEM2YwYjdkN0U1QTlmZkJjZTM2ZDNmOWJkMiIsInJvbGUiOiJub3JtYWwiLCJpYXQiOjE2Mzc3NTQ2ODEsImV4cCI6MTYzNzc1NDc0MX0.Be1eGBvVuFL4fj4pHHqc0yWDledsgS2GP3Jgonmy-xw'; -const accessTokenExp = 1637754741000; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4MWEiLCJldm1BZGRyZXNzIjoiMHhhNTY1M2U4OEQ5YzM1MjM4N2RlRGRDNzliY2Y5OWYwYWRhNjJlOWM2Iiwicm9sZSI6Im5vcm1hbCIsImlhdCI6MTY5NTEzMzMxNywiZXhwIjoxNjk1MTM1MTE3fQ.6s4-ClaPmUMuvYhZ7MaVrSwu-Axzkv1vCkKGtIqnnGo'; +const accessTokenExp = 1695135117000; const refreshToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YjE5QzI4OTBjZjk0N0FEM2YwYjdkN0U1QTlmZkJjZTM2ZDNmOWJkMiIsInJvbGUiOiJyZWZyZXNoIiwiaWF0IjoxNjM3NzU0NjgxLCJleHAiOjE2Mzc3NTQ5ODF9.3SqgsVMyqFPBcem2W9Iog91SWC8cIAFixXBkDue73Rc'; -const refreshTokenExp = 1637754981000; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4MWEiLCJldm1BZGRyZXNzIjoiMHhhNTY1M2U4OEQ5YzM1MjM4N2RlRGRDNzliY2Y5OWYwYWRhNjJlOWM2Iiwicm9sZSI6InJlZnJlc2giLCJpYXQiOjE2OTUxMzMzMTcsImV4cCI6MTY5NTczODExN30.wgdJ_bJs50CL1lGs_sjaBYrYEvsh-h8Qj0Yv1CLpQR4'; +const refreshTokenExp = 1695738117000; describe(`Given the ${Credentials.name} class`, () => { afterAll(() => { jest.useRealTimers(); }); + describe(`when ${Credentials.prototype.getProfileId.name} is invoked`, () => { + it(`should return a profile id`, async () => { + const credentials = new Credentials(undefined, refreshToken); + + expect(credentials.getProfileId()).toBe('0x1a'); + }); + }); + describe(`when ${Credentials.prototype.canRefresh.name} is invoked`, () => { it(`should return true if refresh token is not yet expired`, async () => { const credentials = new Credentials(undefined, refreshToken); diff --git a/packages/client/src/authentication/adapters/Credentials.ts b/packages/client/src/authentication/adapters/Credentials.ts index 0262ebe0ed..b4c4abaa0c 100644 --- a/packages/client/src/authentication/adapters/Credentials.ts +++ b/packages/client/src/authentication/adapters/Credentials.ts @@ -1,5 +1,5 @@ import { DateUtils, invariant } from '@lens-protocol/shared-kernel'; -import jwtDecode, { JwtPayload } from 'jwt-decode'; +import jwtDecode from 'jwt-decode'; export class ClockSkewedError extends Error { name = 'ClockSkewedError' as const; @@ -11,6 +11,14 @@ const TOKEN_EXP_THRESHOLD = DateUtils.secondsToMs(30); const CLOCK_SKEWED_THRESHOLD = DateUtils.secondsToMs(10); +type LensJwtPayload = { + id: string; + evmAddress: string; + role: string; + iat: number; + exp: number; +}; + export class Credentials { constructor( readonly accessToken: string | undefined, @@ -18,7 +26,7 @@ export class Credentials { ) {} checkClock() { - const decodedToken = jwtDecode(this.refreshToken); + const decodedToken = jwtDecode(this.refreshToken); invariant(decodedToken.iat, 'Issued at date should be provided by JWT token'); // check if local time is not too far off from server time @@ -47,8 +55,15 @@ export class Credentials { return now >= tokenExpTimestamp - TOKEN_EXP_THRESHOLD; } + getProfileId(): string { + const decodedToken = jwtDecode(this.refreshToken); + invariant(decodedToken.id, 'ProfileId should be provided by JWT token'); + + return decodedToken.id; + } + private getTokenExpTimestamp(token: string) { - const decodedToken = jwtDecode(token); + const decodedToken = jwtDecode(token); invariant(decodedToken.exp, 'Exp date should be provided by JWT token');