Skip to content

Commit 23958f6

Browse files
feat(auth): LIT-4211 - Add initial auth package
- Define localStorage storage plugin w/ tests
1 parent 163e083 commit 23958f6

File tree

14 files changed

+436
-0
lines changed

14 files changed

+436
-0
lines changed

packages/auth/.babelrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"presets": [
3+
[
4+
"@nx/web/babel",
5+
{
6+
"useBuiltIns": "usage"
7+
}
8+
]
9+
]
10+
}

packages/auth/.eslintrc.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}

packages/auth/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Quick Start
2+
3+
This module provides management of auth methods that are used to control LIT PKPs, and authorization primitives.
4+
5+
### AuthManager
6+
An AuthManager works with `authenticators` (migrated from: @lit-protocol/lit-auth-client) to generate auth material using various methods (see: authenticators documentation).
7+
8+
The `AuthManager` then uses that auth material to create session credentials, and caches the resulting credentials for use with LIT network services. It also validates auth material and session material, and will attempt to get new auth material any time it detects that existing cached credentials have expired.
9+
10+
### node.js / browser
11+
12+
```
13+
yarn add @lit-protocol/lit-auth
14+
```

packages/auth/jest.config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'lit-auth',
4+
preset: '../../jest.preset.js',
5+
globals: {
6+
'ts-jest': {
7+
tsconfig: '<rootDir>/tsconfig.spec.json',
8+
},
9+
},
10+
transform: {
11+
'^.+\\.[t]s$': 'ts-jest',
12+
},
13+
moduleFileExtensions: ['ts', 'js', 'html'],
14+
coverageDirectory: '../../coverage/packages/lit-auth',
15+
moduleNameMapper: {
16+
'^ipfs-unixfs-importer':
17+
'node_modules/ipfs-unixfs-importer/dist/index.min.js',
18+
'^blockstore-core': 'node_modules/blockstore-core/dist/index.min.js',
19+
},
20+
setupFilesAfterEnv: ['../../jest.setup.js'],
21+
};

packages/auth/package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@lit-protocol/lit-auth",
3+
"license": "MIT",
4+
"homepage": "https://github.com/Lit-Protocol/js-sdk",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/LIT-Protocol/js-sdk"
8+
},
9+
"keywords": [
10+
"library"
11+
],
12+
"bugs": {
13+
"url": "https://github.com/LIT-Protocol/js-sdk/issues"
14+
},
15+
"type": "commonjs",
16+
"publishConfig": {
17+
"access": "public",
18+
"directory": "../../dist/packages/lit-auth"
19+
},
20+
"gitHead": "0d7334c2c55f448e91fe32f29edc5db8f5e09e4b",
21+
"tags": [
22+
"universal"
23+
],
24+
"peerDependencies": {
25+
"tslib": "^2.3.0"
26+
},
27+
"browser": {
28+
"crypto": false,
29+
"stream": false
30+
},
31+
"version": "8.0.0-alpha.0",
32+
"main": "./dist/src/index.js",
33+
"typings": "./dist/src/index.d.ts"
34+
}

packages/auth/project.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "lit-auth",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "packages/lit-auth/src",
5+
"projectType": "library",
6+
"targets": {
7+
"build": {
8+
"executor": "@nx/js:tsc",
9+
"outputs": ["{options.outputPath}"],
10+
"options": {
11+
"outputPath": "dist/packages/lit-auth",
12+
"main": "packages/lit-auth/src/index.ts",
13+
"tsConfig": "packages/lit-auth/tsconfig.lib.json",
14+
"assets": ["packages/lit-auth/*.md"],
15+
"updateBuildableProjectDepsInPackageJson": true
16+
}
17+
},
18+
"lint": {
19+
"executor": "@nx/linter:eslint",
20+
"outputs": ["{options.outputFile}"],
21+
"options": {
22+
"lintFilePatterns": ["packages/lit-auth/**/*.ts"]
23+
}
24+
},
25+
"test": {
26+
"executor": "@nx/jest:jest",
27+
"outputs": ["{workspaceRoot}/coverage/packages/lit-auth"],
28+
"options": {
29+
"jestConfig": "packages/lit-auth/jest.config.ts",
30+
"passWithNoTests": true
31+
}
32+
}
33+
},
34+
"tags": []
35+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { localStorage } from './localStorage';
2+
3+
export { localStorage };
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { localStorage as createLocalStorage } from './localStorage';
2+
3+
import type { LitAuthData } from '../types';
4+
5+
describe('localStorage', () => {
6+
let mockLocalStorage: WindowLocalStorage['localStorage'];
7+
8+
beforeEach(() => {
9+
mockLocalStorage = (() => {
10+
const store = new Map<string, string>();
11+
return {
12+
getItem: (key: string) =>
13+
store.has(key) ? store.get(key) ?? null : null,
14+
setItem: (key: string, value: string) => store.set(key, value),
15+
};
16+
})() as unknown as WindowLocalStorage['localStorage'];
17+
});
18+
19+
const appName: string = 'testApp';
20+
const networkName: string = 'testNetwork';
21+
const pkpAddress: string = '0x123';
22+
const authData: LitAuthData = {
23+
credential: 'abc123',
24+
authMethod: 'EthWallet',
25+
};
26+
27+
test('initializes correctly and validates localStorage', () => {
28+
expect(() =>
29+
createLocalStorage({
30+
appName,
31+
networkName,
32+
localStorage: mockLocalStorage,
33+
})
34+
).not.toThrow();
35+
});
36+
37+
test('throws an error if localStorage is missing', () => {
38+
expect(() =>
39+
// @ts-expect-error Stubbing localstorage for error checking
40+
createLocalStorage({ appName, networkName, localStorage: null })
41+
).toThrow('localStorage is not available in this environment');
42+
});
43+
44+
test('writes and reads to/from localStorage correctly', async () => {
45+
mockLocalStorage.setItem(expect.any(String), JSON.stringify(authData));
46+
const storage = createLocalStorage({
47+
appName,
48+
networkName,
49+
localStorage: mockLocalStorage,
50+
});
51+
52+
expect(storage.read({ pkpAddress })).resolves.toEqual(authData);
53+
});
54+
55+
test('returns null when reading nonexistent data', async () => {
56+
const storage = createLocalStorage({
57+
appName,
58+
networkName,
59+
localStorage: mockLocalStorage,
60+
});
61+
const result = await storage.read({ pkpAddress });
62+
63+
expect(result).toBeNull();
64+
});
65+
66+
test('isolates data between different network names', async () => {
67+
const storageNetworkA = createLocalStorage({
68+
appName,
69+
networkName: 'networkA',
70+
localStorage: mockLocalStorage,
71+
});
72+
const storageNetworkB = createLocalStorage({
73+
appName,
74+
networkName: 'networkB',
75+
localStorage: mockLocalStorage,
76+
});
77+
78+
const authDataNetworkA = { ...authData, credential: 'networkA' };
79+
const authDataNetworkB = { ...authData, credential: 'networkB' };
80+
81+
await storageNetworkA.write({
82+
pkpAddress,
83+
authData: authDataNetworkA,
84+
});
85+
86+
expect(storageNetworkA.read({ pkpAddress })).resolves.toEqual(
87+
authDataNetworkA
88+
);
89+
expect(storageNetworkB.read({ pkpAddress })).resolves.toBeNull();
90+
91+
await storageNetworkB.write({
92+
pkpAddress,
93+
authData: authDataNetworkB,
94+
});
95+
96+
expect(storageNetworkA.read({ pkpAddress })).resolves.toEqual(
97+
authDataNetworkA
98+
);
99+
expect(storageNetworkB.read({ pkpAddress })).resolves.toEqual(
100+
authDataNetworkB
101+
);
102+
});
103+
104+
test('isolates data between different app names', async () => {
105+
const storageAppA = createLocalStorage({
106+
appName: 'appA',
107+
networkName,
108+
localStorage: mockLocalStorage,
109+
});
110+
const storageAppB = createLocalStorage({
111+
appName: 'appB',
112+
networkName,
113+
localStorage: mockLocalStorage,
114+
});
115+
116+
const authDataNetworkB = { ...authData, credential: 'networkB' };
117+
118+
await storageAppA.write({ pkpAddress, authData });
119+
expect(storageAppA.read({ pkpAddress })).resolves.toEqual(authData);
120+
expect(storageAppB.read({ pkpAddress })).resolves.toBeNull();
121+
122+
await storageAppB.write({ pkpAddress, authData: authDataNetworkB });
123+
expect(storageAppB.read({ pkpAddress })).resolves.toEqual(authDataNetworkB);
124+
expect(storageAppA.read({ pkpAddress })).resolves.toEqual(authData);
125+
});
126+
});
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import type { LitAuthStorageProvider } from './types';
2+
import type { LitAuthData } from '../types';
3+
4+
const LOCALSTORAGE_LIT_AUTH_PREFIX = 'lit-auth';
5+
6+
interface LocalStorageConfig {
7+
appName: string;
8+
localStorage?: WindowLocalStorage['localStorage'];
9+
networkName: string;
10+
}
11+
12+
function assertLocalstorageValid(
13+
localStorage: unknown
14+
): asserts localStorage is WindowLocalStorage['localStorage'] {
15+
console.log('localStorage is...', localStorage);
16+
if (!localStorage) {
17+
throw new Error('localStorage is not available in this environment');
18+
}
19+
20+
if (typeof localStorage !== 'object') {
21+
throw new Error('localStorage is not an object');
22+
}
23+
24+
if (
25+
!('getItem' in localStorage) ||
26+
typeof localStorage.getItem !== 'function'
27+
) {
28+
throw new Error('localStorage does not have `getItem` method');
29+
}
30+
31+
if (
32+
!('setItem' in localStorage) ||
33+
typeof localStorage.setItem !== 'function'
34+
) {
35+
throw new Error('localStorage does not have `setItem` method');
36+
}
37+
}
38+
39+
/**
40+
* Builds a lookup key for localStorage based on the provided parameters.
41+
* Ensures that all auth data loaded for a given PKP is for the expected LIT network
42+
* in cases where the same environment may be used to communicate w/ multiple networks
43+
*
44+
* @param {object} params - The parameters required to build the lookup key.
45+
* @param {string} params.appName - The name of the application; used to store different auth material for the same PKP on the same domain
46+
* @param {string} params.networkName - The name of the network; used to store different auth material per LIT network
47+
* @param {string} params.pkpAddress - The LIT PKP address.
48+
*
49+
* @returns {string} The generated lookup key for localStorage.
50+
*
51+
* @private
52+
*/
53+
function buildLookupKey({
54+
appName,
55+
networkName,
56+
pkpAddress,
57+
}: {
58+
appName: string;
59+
networkName: string;
60+
pkpAddress: string;
61+
}): string {
62+
return `${LOCALSTORAGE_LIT_AUTH_PREFIX}:${appName}:${networkName}:${pkpAddress}`;
63+
}
64+
65+
export function localStorage({
66+
appName,
67+
networkName,
68+
localStorage = globalThis.localStorage,
69+
}: LocalStorageConfig): LitAuthStorageProvider {
70+
assertLocalstorageValid(localStorage);
71+
72+
return {
73+
config: { appName, networkName, localStorage },
74+
75+
async write({ pkpAddress, authData }) {
76+
localStorage.setItem(
77+
buildLookupKey({
78+
appName,
79+
networkName,
80+
pkpAddress,
81+
}),
82+
JSON.stringify(authData)
83+
);
84+
},
85+
86+
async read({ pkpAddress }): Promise<LitAuthData | null> {
87+
const value = localStorage.getItem(
88+
buildLookupKey({
89+
appName,
90+
networkName,
91+
pkpAddress,
92+
})
93+
);
94+
95+
if (!value) {
96+
// Empty string will be converted to null
97+
return null;
98+
} else {
99+
return JSON.parse(value);
100+
}
101+
},
102+
};
103+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { LitAuthData } from '../types';
2+
3+
export interface LitAuthStorageProvider {
4+
config: unknown;
5+
6+
read<T extends { pkpAddress: string }>(
7+
params: T,
8+
options?: unknown
9+
): Promise<LitAuthData | null>;
10+
11+
write<T extends { pkpAddress: string; authData: LitAuthData }>(
12+
params: T,
13+
options?: unknown
14+
): Promise<void>;
15+
}

0 commit comments

Comments
 (0)