Skip to content

Commit

Permalink
Merge pull request #1055 from lens-protocol/feat/useLogin
Browse files Browse the repository at this point in the history
feat: useLogin hook
  • Loading branch information
cesarenaldi authored Jan 10, 2025
2 parents 3d97a8f + 3b801c7 commit 5109799
Show file tree
Hide file tree
Showing 52 changed files with 896 additions and 403 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pnpm clean
Create a new package:

```bash
pnpm new:lib
pnpm new:package
```

### IDE Setup <!-- omit in toc -->
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
}
},
{
"include": ["./examples/**/*.ts"],
"include": ["./examples/**/*.ts", "./examples/**/*.tsx"],
"linter": {
"rules": {
"style": {
Expand Down
4 changes: 2 additions & 2 deletions examples/create-app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'viem/window';
import { chains } from '@lens-network/sdk/viem';
import { PublicClient, testnet as protocolTestnet } from '@lens-protocol/client';
import { createApp, fetchApp } from '@lens-protocol/client/actions';
import { handleWith } from '@lens-protocol/client/viem';
import { handleOperationWith } from '@lens-protocol/client/viem';
import { Platform, app } from '@lens-protocol/metadata';
import { StorageClient, testnet as storageTestnet } from '@lens-protocol/storage-node-client';
import { type Address, createWalletClient, custom } from 'viem';
Expand Down Expand Up @@ -53,7 +53,7 @@ const created = await createApp(sessionClient, {
metadataUri: uri,
verification: false, // will become optional soon
})
.andThen(handleWith(walletClient))
.andThen(handleOperationWith(walletClient))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchApp(sessionClient, { txHash }))
.match(
Expand Down
4 changes: 2 additions & 2 deletions examples/user-onboarding/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'viem/window';
import { chains } from '@lens-network/sdk/viem';
import { PublicClient, testnet as protocolTestnet } from '@lens-protocol/client';
import { createAccountWithUsername, fetchAccount } from '@lens-protocol/client/actions';
import { handleWith } from '@lens-protocol/client/viem';
import { handleOperationWith } from '@lens-protocol/client/viem';
import { account } from '@lens-protocol/metadata';
import { StorageClient, testnet as storageTestnet } from '@lens-protocol/storage-node-client';
import { type Address, createWalletClient, custom } from 'viem';
Expand Down Expand Up @@ -54,7 +54,7 @@ const created = await createAccountWithUsername(sessionClient, {
localName: `john.doe.${Date.now()}`,
},
})
.andThen(handleWith(walletClient))
.andThen(handleOperationWith(walletClient))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchAccount(sessionClient, { txHash }))
.match(
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"scripts": {
"build": "turbo build",
"dev": "turbo watch build",
"clean": "rimraf packages/*/dist",
"clean": "rimraf .turbo packages/*/dist",
"lint": "biome check",
"lint:fix": "biome check --write",
"new:lib": "NODE_OPTIONS='--import tsx' plop --plopfile=plopfile.ts",
"new:package": "NODE_OPTIONS='--import tsx' plop --plopfile=plopfile.ts",
"prepublish": "pnpm run build",
"test:client": "vitest --project @lens-protocol/client --no-file-parallelism",
"test:react": "vitest --project @lens-protocol/react --no-file-parallelism",
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"msw": "^2.7.0",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
"viem": "^2.21.53",
"viem": "^2.22.4",
"zksync-ethers": "^6.15.3",
"zod": "^3.23.8"
},
Expand Down
78 changes: 31 additions & 47 deletions packages/client/src/AuthenticatedUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,82 +3,66 @@ import { type EvmAddress, type Result, type UUID, err, never, ok } from '@lens-p
import { UnexpectedError } from './errors';
import type { IdTokenClaims } from './tokens';

export type AccountManager = {
role: Role.AccountManager;
authentication_id: UUID;
account: EvmAddress;
app: EvmAddress;
manager: EvmAddress;
sponsored: boolean;
};

export type AccountOwner = {
role: Role.AccountOwner;
authentication_id: UUID;
account: EvmAddress;
app: EvmAddress;
owner: EvmAddress;
sponsored: boolean;
};
const ROLE_CLAIM = 'tag:lens.dev,2024:role';
const SPONSORED_CLAIM = 'tag:lens.dev,2024:sponsored';

export type OnboardingUser = {
role: Role.OnboardingUser;
authentication_id: UUID;
export type AuthenticatedUser = {
address: EvmAddress;
app: EvmAddress;
authenticationId: UUID;
role: Role;
signer: EvmAddress;
sponsored: boolean;
};

export type Builder = {
role: Role.Builder;
authentication_id: UUID;
address: EvmAddress;
sponsored: boolean;
};

export type AuthenticatedUser = AccountManager | AccountOwner | OnboardingUser | Builder;

/**
* @internal
*/
export function authenticatedUser(
claims: IdTokenClaims,
): Result<AuthenticatedUser, UnexpectedError> {
switch (claims['tag:lens.dev,2024:role']) {
switch (claims[ROLE_CLAIM]) {
case Role.AccountManager:
return ok({
role: Role.AccountManager,
authentication_id: claims.sid,
account: claims.act?.sub ?? never('Account Manager must have an Actor Claim'),
address: claims.act?.sub ?? never('Account Manager must have an Actor Claim'),
app: claims.aud,
manager: claims.sub,
sponsored: claims['tag:lens.dev,2024:sponsored'],
authenticationId: claims.sid,
role: Role.AccountManager,
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});

case Role.AccountOwner:
return ok({
role: Role.AccountOwner,
authentication_id: claims.sid,
account: claims.act?.sub ?? never('Account Owner must have an Actor Claim'),
address: claims.act?.sub ?? never('Account Owner must have an Actor Claim'),
app: claims.aud,
owner: claims.sub,
sponsored: claims['tag:lens.dev,2024:sponsored'],
authenticationId: claims.sid,
role: Role.AccountOwner,
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});

case Role.OnboardingUser:
return ok({
role: Role.OnboardingUser,
authentication_id: claims.sid,
address: claims.sub,
app: claims.aud,
sponsored: claims['tag:lens.dev,2024:sponsored'],
authenticationId: claims.sid,
role: Role.OnboardingUser,
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});

case Role.Builder:
return ok({
role: Role.Builder,
authentication_id: claims.sid,
address: claims.sub,
sponsored: claims['tag:lens.dev,2024:sponsored'],
app: claims.aud,
authenticationId: claims.sid,
role: Role.Builder,
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});

default:
return err(UnexpectedError.from(`Unexpected role: ${claims['tag:lens.dev,2024:role']}`));
return err(UnexpectedError.from(`Unexpected role: ${claims[ROLE_CLAIM]}`));
}
}
6 changes: 3 additions & 3 deletions packages/client/src/actions/accountManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as metadata from '@lens-protocol/metadata';
import { assertOk, never, uri } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import { loginAsOnboardingUser, storageClient, wallet } from '../test-utils';
import { handleWith } from '../viem';
import { handleOperationWith } from '../viem';
import {
createAccountWithUsername,
enableSignless,
Expand All @@ -27,7 +27,7 @@ describe('Given a new Lens Account', () => {
username: { localName: `testname${Date.now()}` },
metadataUri: uri(`data:application/json,${JSON.stringify(initialMetadata)}`), // empty at first
})
.andThen(handleWith(wallet))
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchAccount(sessionClient, { txHash }))
.andThen((account) => {
Expand All @@ -46,7 +46,7 @@ describe('Given a new Lens Account', () => {
describe(`When invoking the '${enableSignless.name}' action`, () => {
beforeAll(async () => {
const result = await enableSignless(sessionClient)
.andThen(handleWith(wallet))
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction);
assertOk(result);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/client/src/actions/onboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest';
import { type Account, Role } from '@lens-protocol/graphql';
import { uri } from '@lens-protocol/types';
import { loginAsOnboardingUser, signer, wallet } from '../test-utils';
import { handleWith } from '../viem';
import { handleOperationWith } from '../viem';
import { createAccountWithUsername, fetchAccount } from './account';

const metadata = account({
Expand All @@ -26,7 +26,7 @@ describe('Given an onboarding user', () => {
metadataUri: uri(`data:application/json,${JSON.stringify(metadata)}`),
})
// Sign if necessary
.andThen(handleWith(wallet))
.andThen(handleOperationWith(wallet))

// Wait for the transaction to be mined
.andThen(sessionClient.waitForTransaction)
Expand All @@ -50,8 +50,8 @@ describe('Given an onboarding user', () => {
const user = await result.value.getAuthenticatedUser().unwrapOr(null);
expect(user).toMatchObject({
role: Role.AccountOwner,
account: newAccount!.address.toLowerCase(),
owner: signer.toLowerCase(),
address: newAccount!.address.toLowerCase(),
signer: signer.toLowerCase(),
});
});
});
Expand Down
65 changes: 43 additions & 22 deletions packages/client/src/clients.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { url, assertErr, assertOk, evmAddress, signatureFrom } from '@lens-protocol/types';
import { CurrentSessionQuery, HealthQuery, RefreshMutation, Role } from '@lens-protocol/graphql';
import { url, assertErr, assertOk, signatureFrom } from '@lens-protocol/types';
import { HttpResponse, graphql, passthrough } from 'msw';
import { setupServer } from 'msw/node';

import { privateKeyToAccount } from 'viem/accounts';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { CurrentSessionQuery, HealthQuery, RefreshMutation, Role } from '@lens-protocol/graphql';
import { currentSession } from './actions';
import { PublicClient } from './clients';
import { GraphQLErrorCode, UnauthenticatedError, UnexpectedError } from './errors';
import { createGraphQLErrorObject, createPublicClient } from './test-utils';
import {
account,
app,
createGraphQLErrorObject,
createPublicClient,
signer,
wallet,
} from './test-utils';
import { delay } from './utils';

const signer = privateKeyToAccount(import.meta.env.PRIVATE_KEY);
const owner = evmAddress(signer.address);
const account = evmAddress(import.meta.env.TEST_ACCOUNT);
const app = evmAddress(import.meta.env.TEST_APP);
import { signMessageWith } from './viem';

describe(`Given an instance of the ${PublicClient.name}`, () => {
const client = createPublicClient();
Expand All @@ -25,15 +27,15 @@ describe(`Given an instance of the ${PublicClient.name}`, () => {
const challenge = await client.challenge({
accountOwner: {
account,
owner,
owner: signer,
app,
},
});
assertOk(challenge);

const authenticated = await client.authenticate({
id: challenge.value.id,
signature: signatureFrom(await signer.signMessage({ message: challenge.value.text })),
signature: signatureFrom(await wallet.signMessage({ message: challenge.value.text })),
});

assertOk(authenticated);
Expand All @@ -42,18 +44,18 @@ describe(`Given an instance of the ${PublicClient.name}`, () => {
assertOk(user);
expect(user.value).toMatchObject({
role: Role.AccountOwner,
account: account.toLowerCase(),
owner: owner.toLowerCase(),
address: account.toLowerCase(),
signer: signer.toLowerCase(),
});
});
});

describe('When authenticating via the `login` convenience method', () => {
describe(`When authenticating via the '${PublicClient.prototype.login.name}' convenience method`, () => {
it('Then it should return an Err<never, SigningError> with any error thrown by the provided `SignMessage` function', async () => {
const authenticated = await client.login({
accountOwner: {
account,
owner,
owner: signer,
app,
},
signMessage: async () => {
Expand All @@ -70,18 +72,18 @@ describe(`Given an instance of the ${PublicClient.name}`, () => {
await client.login({
accountOwner: {
account,
owner,
owner: signer,
app,
},
signMessage: (message) => signer.signMessage({ message }),
signMessage: signMessageWith(wallet),
});

const authenticated = await client.resumeSession();
assertOk(authenticated);

const authentication = await currentSession(authenticated.value);
expect(authentication._unsafeUnwrap()).toMatchObject({
signer: owner,
signer,
app,
});
});
Expand All @@ -106,6 +108,25 @@ describe(`Given an instance of the ${PublicClient.name}`, () => {
});

describe('And a SessionClient created from it', () => {
describe(`When invoking the 'logout' method`, () => {
it('Then it should revoke the current authenticated session and clear the credentials from the storage', async () => {
const authenticated = await client.login({
accountOwner: {
account,
owner: signer,
app,
},
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);

const result = await authenticated.value.logout();
assertOk(result);
assertErr(await currentSession(authenticated.value));
assertErr(await authenticated.value.getAuthenticatedUser());
});
});

describe('When a request fails with UNAUTHENTICATED extension code', () => {
const server = setupServer(
graphql.query(
Expand Down Expand Up @@ -137,10 +158,10 @@ describe(`Given an instance of the ${PublicClient.name}`, () => {
const authenticated = await client.login({
accountOwner: {
account,
owner,
owner: signer,
app,
},
signMessage: (message) => signer.signMessage({ message }),
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);

Expand Down Expand Up @@ -184,10 +205,10 @@ describe(`Given an instance of the ${PublicClient.name}`, () => {
const authenticated = await client.login({
accountOwner: {
account,
owner,
owner: signer,
app,
},
signMessage: (message) => signer.signMessage({ message }),
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);

Expand Down
Loading

0 comments on commit 5109799

Please sign in to comment.