Skip to content

Commit

Permalink
feat: authentication domain changes
Browse files Browse the repository at this point in the history
  • Loading branch information
cesarenaldi committed Oct 4, 2023
1 parent 7892578 commit 5dbfc68
Show file tree
Hide file tree
Showing 43 changed files with 370 additions and 686 deletions.
10 changes: 5 additions & 5 deletions packages/domain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@
"module": "./use-cases/wallets/dist/lens-protocol-domain-use-cases-wallets.esm.js",
"default": "./use-cases/wallets/dist/lens-protocol-domain-use-cases-wallets.cjs.js"
},
"./use-cases/lifecycle": {
"module": "./use-cases/lifecycle/dist/lens-protocol-domain-use-cases-lifecycle.esm.js",
"default": "./use-cases/lifecycle/dist/lens-protocol-domain-use-cases-lifecycle.cjs.js"
},
"./use-cases/publications": {
"module": "./use-cases/publications/dist/lens-protocol-domain-use-cases-publications.esm.js",
"default": "./use-cases/publications/dist/lens-protocol-domain-use-cases-publications.cjs.js"
Expand All @@ -39,6 +35,10 @@
"module": "./use-cases/transactions/dist/lens-protocol-domain-use-cases-transactions.esm.js",
"default": "./use-cases/transactions/dist/lens-protocol-domain-use-cases-transactions.cjs.js"
},
"./use-cases/authentication": {
"module": "./use-cases/authentication/dist/lens-protocol-domain-use-cases-authentication.esm.js",
"default": "./use-cases/authentication/dist/lens-protocol-domain-use-cases-authentication.cjs.js"
},
"./package.json": "./package.json"
},
"repository": {
Expand Down Expand Up @@ -124,7 +124,7 @@
"preconstruct": {
"entrypoints": [
"entities/index.ts",
"use-cases/lifecycle/index.ts",
"use-cases/authentication/index.ts",
"use-cases/inbox/index.ts",
"use-cases/profile/index.ts",
"use-cases/polls/index.ts",
Expand Down
7 changes: 6 additions & 1 deletion packages/domain/src/entities/Credentials.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { EvmAddress } from '@lens-protocol/shared-kernel';

import { ProfileId } from './Profile';

export interface ICredentials {
readonly address: string;
readonly address: EvmAddress;
readonly profileId: ProfileId;

isExpired(): boolean;
}
1 change: 1 addition & 0 deletions packages/domain/src/entities/__helpers__/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function mockWallet({ address = mockEvmAddress() }: { address?: EvmAddres
export function mockCredentials(overrides?: Partial<ICredentials>) {
return mock<ICredentials>({
address: mockEvmAddress(),
profileId: mockProfileId(),
...overrides,
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/domain/src/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './entities/__helpers__/mocks';
export * from './use-cases/authentication/__helpers__/mocks';
export * from './use-cases/polls/__helpers__/mocks';
export * from './use-cases/profile/__helpers__/mocks';
export * from './use-cases/publications/__helpers__/mocks';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { invariant } from '@lens-protocol/shared-kernel';
import { invariant, never } from '@lens-protocol/shared-kernel';

import { ICredentials, Wallet } from '../../entities';

Expand All @@ -16,11 +16,11 @@ export class ActiveWallet {
private walletGateway: IReadableWalletGateway,
) {}

async getActiveWallet(): Promise<Wallet | null> {
async requireActiveWallet(): Promise<Wallet> {
const credentials = await this.credentialsReader.getCredentials();

if (!credentials) {
return null;
never('User is not authenticated');
}

const wallet = await this.walletGateway.getByAddress(credentials.address);
Expand All @@ -29,16 +29,4 @@ export class ActiveWallet {

return wallet;
}

async requireActiveWallet(): Promise<Wallet> {
const wallet = await this.getActiveWallet();

invariant(
wallet,
`Active wallet is not defined.
Make sure that the storage configuration (IStorageProvider) properly persists saves`,
);

return wallet;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { PromiseResult } from '@lens-protocol/shared-kernel';

import { ICredentials, AnyTransactionRequestModel, Wallet } from '../../entities';
import { ActiveProfileLoader } from '../profile';
import { ICredentials, AnyTransactionRequestModel } from '../../entities';
import { TransactionQueue } from '../transactions';
import {
ActiveWallet,
ICredentialsReader,
ICredentialsWriter,
LogoutReason,
WalletLogout,
} from '../wallets';
import { ISessionPresenter } from './ISessionPresenter';
import { ICredentialsReader } from './ActiveWallet';
import { ICredentialsWriter } from './Login';
import { Logout, LogoutReason } from './Logout';
import { SessionData } from './SessionData';

export class CredentialsExpiredError extends Error {
name = 'CredentialsExpiredError' as const;
Expand All @@ -23,52 +18,43 @@ export interface ICredentialsRenewer {
renewCredentials(credentials: ICredentials): PromiseResult<ICredentials, CredentialsExpiredError>;
}

export interface IBootstrapPresenter {
anonymous(): void;

authenticated(session: SessionData): void;
}

export class Bootstrap {
constructor(
private readonly activeWallet: ActiveWallet,
private readonly credentialsGateway: ICredentialsGateway,
private readonly credentialsRenewer: ICredentialsRenewer,
private readonly activeProfileLoader: ActiveProfileLoader,
private readonly transactionQueue: TransactionQueue<AnyTransactionRequestModel>,
private readonly sessionPresenter: ISessionPresenter,
private readonly walletLogout: WalletLogout,
private readonly logout: Logout,
private readonly presenter: IBootstrapPresenter,
) {}

async execute() {
const wallet = await this.activeWallet.getActiveWallet();

if (!wallet) {
this.sessionPresenter.anonymous();
return;
}

const credentials = await this.credentialsGateway.getCredentials();

if (!credentials) {
await this.logout();
this.presenter.anonymous();
return;
}

const result = await this.credentialsRenewer.renewCredentials(credentials);

if (result.isFailure()) {
await this.logout();
await this.logout.logout(LogoutReason.CREDENTIALS_EXPIRED);
return;
}

await this.credentialsGateway.save(result.value);

await this.authenticated(wallet);
}

private async authenticated(wallet: Wallet) {
const profile = await this.activeProfileLoader.loadActiveProfileByOwnerAddress(wallet.address);

this.sessionPresenter.authenticated(wallet, profile);

await this.transactionQueue.resume();
}

private async logout() {
await this.walletLogout.logout(LogoutReason.CREDENTIALS_EXPIRED);
this.presenter.authenticated({
address: credentials.address,
profileId: credentials.profileId,
});
}
}
72 changes: 72 additions & 0 deletions packages/domain/src/use-cases/authentication/Login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { EvmAddress, failure, PromiseResult, Result, success } from '@lens-protocol/shared-kernel';

import {
ICredentials,
PendingSigningRequestError,
ProfileId,
UserRejectedError,
Wallet,
WalletConnectionError,
} from '../../entities';
import { SessionData } from './SessionData';

/**
* The details required to authenticate the session.
*/
export type LoginRequest = {
/**
* The Profile Owner or an authorized Profile Manager.
*/
address: EvmAddress;
/**
* The authenticated Profile ID.
*/
profileId: ProfileId;
};

export interface IWalletFactory {
create(address: EvmAddress): Promise<Wallet>;
}

export interface IWritableWalletGateway {
save(wallet: Wallet): Promise<void>;
}

export type LoginError = PendingSigningRequestError | UserRejectedError | WalletConnectionError;

export interface ILoginPresenter {
present(result: Result<SessionData, LoginError>): void;
}

export interface ICredentialsIssuer {
issueCredentials(request: LoginRequest): PromiseResult<ICredentials, LoginError>;
}

export interface ICredentialsWriter {
save(credentials: ICredentials): Promise<void>;
}

export class Login {
constructor(
private readonly walletFactory: IWalletFactory,
private readonly walletGateway: IWritableWalletGateway,
private readonly credentialsIssuer: ICredentialsIssuer,
private readonly credentialsWriter: ICredentialsWriter,
private readonly presenter: ILoginPresenter,
) {}

async login(request: LoginRequest): Promise<void> {
const wallet = await this.walletFactory.create(request.address);
const result = await this.credentialsIssuer.issueCredentials(request);

if (result.isFailure()) {
this.presenter.present(failure(result.error));
return;
}

await this.walletGateway.save(wallet);
await this.credentialsWriter.save(result.value);

this.presenter.present(success(request));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import type { ISessionPresenter } from '../lifecycle/ISessionPresenter';
import { ActiveWallet } from './ActiveWallet';

/**
* The reason for logging out
*/
Expand All @@ -17,33 +14,28 @@ export interface IResettableWalletGateway {
reset(): Promise<void>;
}

export interface IActiveProfileGateway {
export interface IConversationsGateway {
reset(): Promise<void>;
}

export interface IConversationsGateway {
reset(): Promise<void>;
export interface ILogoutPresenter {
logout(reason: LogoutReason): void;
}

export class WalletLogout {
export class Logout {
constructor(
private walletGateway: IResettableWalletGateway,
private credentialsGateway: IResettableCredentialsGateway,
private activeWallet: ActiveWallet,
private activeProfileGateway: IActiveProfileGateway,
private conversationsGateway: IConversationsGateway,
private sessionPresenter: ISessionPresenter,
private presenter: ILogoutPresenter,
) {}

async logout(reason: LogoutReason): Promise<void> {
const activeWallet = await this.activeWallet.requireActiveWallet();

await this.walletGateway.reset();
await this.activeProfileGateway.reset();
await this.conversationsGateway.reset();

await this.credentialsGateway.invalidate();

this.sessionPresenter.logout({ lastLoggedInWallet: activeWallet, logoutReason: reason });
this.presenter.logout(reason);
}
}
17 changes: 17 additions & 0 deletions packages/domain/src/use-cases/authentication/SessionData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EvmAddress } from '@lens-protocol/shared-kernel';

import { ProfileId } from '../../entities';

/**
* Describes the details of an authenticated session.
*/
export type SessionData = {
/**
* The Profile Owner or an authorized Profile Manager.
*/
address: EvmAddress;
/**
* The authenticated Profile ID.
*/
profileId: ProfileId;
};
23 changes: 23 additions & 0 deletions packages/domain/src/use-cases/authentication/__helpers__/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { mockEvmAddress } from '@lens-protocol/shared-kernel/mocks';
import { mock } from 'jest-mock-extended';
import { when } from 'jest-when';

import { Wallet } from '../../../entities';
import { mockProfileId, mockWallet } from '../../../entities/__helpers__/mocks';
import { ActiveWallet } from '../ActiveWallet';
import { LoginRequest } from '../Login';

export function mockLoginRequest(): LoginRequest {
return {
address: mockEvmAddress(),
profileId: mockProfileId(),
};
}

export function mockActiveWallet({ wallet = mockWallet() }: { wallet?: Wallet } = {}) {
const activeWallet = mock<ActiveWallet>();

when(activeWallet.requireActiveWallet).mockResolvedValue(wallet);

return activeWallet;
}
Loading

0 comments on commit 5dbfc68

Please sign in to comment.