From cef133031e501f8f73c66a379de38b1041287743 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 14 Jun 2022 14:29:47 -0500 Subject: [PATCH] Add base auth & ucan request flow (web only) --- .env | 1 + README.md | 20 +- package.json | 10 +- scripts/testing-server.mjs | 21 +- src/api/auth.ts | 48 ++ src/api/index.ts | 37 +- src/env.ts | 5 + src/screens/Home.tsx | 2 +- src/screens/Login.tsx | 9 +- src/screens/Signup.tsx | 6 +- src/state/env.ts | 6 +- src/state/index.ts | 12 + src/state/models/session.ts | 89 +- yarn.lock | 1579 ++++++++++++++++++++++++++++++----- 14 files changed, 1555 insertions(+), 290 deletions(-) create mode 100644 .env create mode 100644 src/api/auth.ts create mode 100644 src/env.ts diff --git a/.env b/.env new file mode 100644 index 00000000..bed6a2d8 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_AUTH_LOBBY = 'http://localhost:3001' diff --git a/README.md b/README.md index d7e93620..779a7855 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,16 @@ Uses: - Setup your environment [using the react native instructions](https://reactnative.dev/docs/environment-setup). - After initial setup: - - `cd ios ; pod install` Installs the React Navigation deps ([info](https://reactnative.dev/docs/navigation#installation-and-setup)). -- To run the iOS simulator: `yarn ios` -- To run the Android simulator: `yarn android` -- To run the Web app: `yarn web` + - `cd ios ; pod install` +- Start the dev servers + - `yarn dev-pds` + - `yarn dev-wallet` +- Run the dev app + - iOS: `yarn ios` + - Android: `yarn android` + - Web: `yarn web` - Tips - `npx react-native info` Checks what has been installed. - - Android instructions are a *little* inaccurate but not as much as you might think. I had to manually create a virtual device, then run `yarn android` twice (once to start the emulator and the second time to connect to it). - -## TODOs - -- API - - Create mock api -- Tests - - Should just try to catch errors on basic load ## Various notes diff --git a/package.json b/package.json index cfa1f892..9c7897f0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "app", + "name": "pubsq", "version": "0.0.1", "private": true, "scripts": { @@ -7,11 +7,14 @@ "ios": "react-native run-ios", "web": "react-scripts start", "start": "react-native start", - "dev-backend": "node ./scripts/testing-server.mjs", + "dev-pds": "node ./scripts/testing-server.mjs", + "dev-wallet": "cd node_modules/\\@adxp/auth-lobby && npm run start:authed", "test": "jest", "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, "dependencies": { + "@adxp/auth": "*", + "@adxp/common": "*", "@react-native-async-storage/async-storage": "^1.17.6", "@react-navigation/bottom-tabs": "^6.3.1", "@react-navigation/native": "^6.0.10", @@ -29,6 +32,9 @@ "ucans": "0.9.0-alpha3" }, "devDependencies": { + "@adxp/auth-lobby": "*", + "@adxp/server": "*", + "@adxp/ws-relay": "*", "@babel/core": "^7.12.9", "@babel/runtime": "^7.12.5", "@react-native-community/eslint-config": "^2.0.0", diff --git a/scripts/testing-server.mjs b/scripts/testing-server.mjs index 3517acdf..adc214cf 100644 --- a/scripts/testing-server.mjs +++ b/scripts/testing-server.mjs @@ -1,16 +1,25 @@ -import {IpldStore} from '@adx/common' -import server from '@adx/server/dist/server.js' -import Database from '@adx/server/dist/db/index.js' +import {IpldStore} from '@adxp/common' +import PDSServer from '@adxp/server/dist/server.js' +import PDSDatabase from '@adxp/server/dist/db/index.js' +import WSRelayServer from '@adxp/ws-relay/dist/index.js' -const PORT = 1986 +const PDS_PORT = 2583 +const WSR_PORT = 3005 async function start() { console.log('Initializing...') - const db = Database.memory() + const db = PDSDatabase.memory() const serverBlockstore = IpldStore.createInMemory() await db.dropTables() await db.createTables() - server(serverBlockstore, db, PORT) + PDSServer(serverBlockstore, db, PDS_PORT) + + if (process.argv.includes('--relay')) { + WSRelayServer(WSR_PORT) + console.log(`🔁 Relay server running on port ${WSR_PORT}`) + } else { + console.log('Include --relay to start the WS Relay') + } } start() diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 00000000..2da8f2cc --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,48 @@ +import * as auth from '@adxp/auth' +import {isWeb} from '../platform/detection' +import * as env from '../env' + +const SCOPE = auth.writeCap( + 'did:key:z6MkfRiFMLzCxxnw6VMrHK8pPFt4QAHS3jX3XM87y9rta6kP', + 'did:example:microblog', +) + +export async function isAuthed(authStore: auth.BrowserStore) { + return await authStore.hasUcan(SCOPE) +} + +export async function logout(authStore: auth.BrowserStore) { + await authStore.reset() +} + +export async function parseUrlForUcan() { + // @ts-ignore window is defined -prf + const fragment = window.location.hash + if (fragment.length < 1) { + return undefined + } + try { + const ucan = await auth.parseLobbyResponseHashFragment(fragment) + // @ts-ignore window is defined -prf + window.location.hash = '' + return ucan + } catch (err) { + return undefined + } +} + +export async function requestAppUcan(authStore: auth.BrowserStore) { + const did = await authStore.getDid() + if (isWeb) { + // @ts-ignore window is defined -prf + const redirectTo = window.location.origin + const fragment = auth.requestAppUcanHashFragment(did, SCOPE, redirectTo) + // @ts-ignore window is defined -prf + window.location.href = `${env.AUTH_LOBBY}#${fragment}` + return false + } else { + // TODO + console.log('TODO') + } + return false +} diff --git a/src/api/index.ts b/src/api/index.ts index f83e6541..6f0dc0b3 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,26 @@ -import {MicroblogDelegator, MicroblogReader, auth} from '@adx/common' -import * as ucan from 'ucans' +// import {MicroblogDelegator, MicroblogReader, auth} from '@adx/common' +// import * as ucan from 'ucans' + +class MicroblogReader { + constructor(public url: string, public did: any) {} +} +class MicroblogDelegator { + constructor( + public url: string, + public did: any, + public keypair: any, + public ucanStore: any, + ) {} +} +const auth = { + async claimFull(_one: any, _two: any) { + return { + encoded() { + return 'todo' + }, + } + }, +} export class API { userCfg?: UserConfig @@ -51,9 +72,9 @@ export interface SerializedUserConfig { export class UserConfig { serverUrl?: string did?: string - keypair?: ucan.EdKeypair + keypair?: any //ucan.EdKeypair rootAuthToken?: string - ucanStore?: ucan.Store + ucanStore?: any //ucan.Store get hasWriteCaps() { return Boolean(this.did && this.keypair && this.ucanStore) @@ -62,10 +83,10 @@ export class UserConfig { static async createTest(serverUrl: string) { const cfg = new UserConfig() cfg.serverUrl = serverUrl - cfg.keypair = await ucan.EdKeypair.create() + cfg.keypair = true //await ucan.EdKeypair.create() cfg.did = cfg.keypair.did() cfg.rootAuthToken = (await auth.claimFull(cfg.did, cfg.keypair)).encoded() - cfg.ucanStore = await ucan.Store.fromTokens([cfg.rootAuthToken]) + cfg.ucanStore = true // await ucan.Store.fromTokens([cfg.rootAuthToken]) return cfg } @@ -88,10 +109,10 @@ export class UserConfig { async hydrate(state: SerializedUserConfig) { this.serverUrl = state.serverUrl if (state.secretKeyStr && state.rootAuthToken) { - this.keypair = ucan.EdKeypair.fromSecretKey(state.secretKeyStr) + this.keypair = true // ucan.EdKeypair.fromSecretKey(state.secretKeyStr) this.did = this.keypair.did() this.rootAuthToken = state.rootAuthToken - this.ucanStore = await ucan.Store.fromTokens([this.rootAuthToken]) + this.ucanStore = true // await ucan.Store.fromTokens([this.rootAuthToken]) } } } diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 00000000..78fb88ac --- /dev/null +++ b/src/env.ts @@ -0,0 +1,5 @@ +if (typeof process.env.REACT_APP_AUTH_LOBBY !== 'string') { + throw new Error('ENV: No auth lobby provided') +} + +export const AUTH_LOBBY = process.env.REACT_APP_AUTH_LOBBY diff --git a/src/screens/Home.tsx b/src/screens/Home.tsx index 90c9262f..ed95121e 100644 --- a/src/screens/Home.tsx +++ b/src/screens/Home.tsx @@ -14,7 +14,7 @@ export function Home({navigation}: RootTabsScreenProps<'Home'>) { title="Go to Jane's profile" onPress={() => navigation.navigate('Profile', {name: 'Jane'})} /> -