diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-06-14 14:29:47 -0500 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-06-14 14:29:47 -0500 |
commit | cef133031e501f8f73c66a379de38b1041287743 (patch) | |
tree | b628150cbee4facbcbfc06599baaf4c108949995 /src | |
parent | 09b78a46343088a2790dab067bd4fd8384957311 (diff) | |
download | voidsky-cef133031e501f8f73c66a379de38b1041287743.tar.zst |
Add base auth & ucan request flow (web only)
Diffstat (limited to 'src')
-rw-r--r-- | src/api/auth.ts | 48 | ||||
-rw-r--r-- | src/api/index.ts | 37 | ||||
-rw-r--r-- | src/env.ts | 5 | ||||
-rw-r--r-- | src/screens/Home.tsx | 2 | ||||
-rw-r--r-- | src/screens/Login.tsx | 9 | ||||
-rw-r--r-- | src/screens/Signup.tsx | 6 | ||||
-rw-r--r-- | src/state/env.ts | 6 | ||||
-rw-r--r-- | src/state/index.ts | 12 | ||||
-rw-r--r-- | src/state/models/session.ts | 85 |
9 files changed, 161 insertions, 49 deletions
diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 000000000..2da8f2cc7 --- /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 f83e65411..6f0dc0b38 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 000000000..78fb88acc --- /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 90c9262f3..ed95121ea 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'})} /> - <Button title="Logout" onPress={() => store.session.setAuthed(false)} /> + <Button title="Logout" onPress={() => store.session.logout()} /> </View> </Shell> ) diff --git a/src/screens/Login.tsx b/src/screens/Login.tsx index 8451eb3c8..36280e87a 100644 --- a/src/screens/Login.tsx +++ b/src/screens/Login.tsx @@ -12,14 +12,9 @@ export const Login = observer(({navigation}: RootTabsScreenProps<'Login'>) => { <View style={{justifyContent: 'center', alignItems: 'center'}}> <Text style={{fontSize: 20, fontWeight: 'bold'}}>Sign In</Text> {store.session.uiError ?? <Text>{store.session.uiError}</Text>} - {store.session.uiState === 'idle' ? ( + {!store.session.uiIsProcessing ? ( <> - {store.session.hasAccount ?? ( - <Button - title="Login" - onPress={() => store.session.loadAccount()} - /> - )} + <Button title="Login" onPress={() => store.session.login()} /> <Button title="Sign Up" onPress={() => navigation.navigate('Signup')} diff --git a/src/screens/Signup.tsx b/src/screens/Signup.tsx index 1d5915d65..e09ab5dd6 100644 --- a/src/screens/Signup.tsx +++ b/src/screens/Signup.tsx @@ -13,13 +13,11 @@ export const Signup = observer( <View style={{justifyContent: 'center', alignItems: 'center'}}> <Text style={{fontSize: 20, fontWeight: 'bold'}}>Create Account</Text> {store.session.uiError ?? <Text>{store.session.uiError}</Text>} - {store.session.uiState === 'idle' ? ( + {!store.session.uiIsProcessing ? ( <> <Button title="Create new account" - onPress={() => - store.session.createTestAccount('http://localhost:1986') - } + onPress={() => store.session.login()} /> <Button title="Log in to an existing account" diff --git a/src/state/env.ts b/src/state/env.ts index 59c0554a2..f47a7037f 100644 --- a/src/state/env.ts +++ b/src/state/env.ts @@ -4,14 +4,18 @@ */ import {getEnv, IStateTreeNode} from 'mobx-state-tree' +import * as auth from '@adxp/auth' import {API} from '../api' export class Environment { api = new API() + authStore?: auth.BrowserStore constructor() {} - async setup() {} + async setup() { + this.authStore = await auth.BrowserStore.load() + } } /** diff --git a/src/state/index.ts b/src/state/index.ts index 496f3631d..0e70055e0 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -6,6 +6,7 @@ import { } from './models/root-store' import {Environment} from './env' import * as storage from './storage' +import * as auth from '../api/auth' const ROOT_STATE_STORAGE_KEY = 'root' @@ -14,6 +15,7 @@ export async function setupState() { let data: any const env = new Environment() + await env.setup() try { data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {} rootStore = RootStoreModel.create(data, env) @@ -27,6 +29,16 @@ export async function setupState() { storage.save(ROOT_STATE_STORAGE_KEY, snapshot), ) + if (env.authStore) { + const isAuthed = await auth.isAuthed(env.authStore) + rootStore.session.setAuthed(isAuthed) + const ucan = await auth.parseUrlForUcan() + if (ucan) { + await env.authStore.addUcan(ucan) + rootStore.session.setAuthed(true) + } + } + return rootStore } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index a550e2174..06c4bb1aa 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -1,12 +1,13 @@ import {Instance, SnapshotOut, types, flow} from 'mobx-state-tree' -import {UserConfig} from '../../api' +// import {UserConfig} from '../../api' +import * as auth from '../../api/auth' import {withEnvironment} from '../env' export const SessionModel = types .model('Session') .props({ isAuthed: types.boolean, - uiState: types.enumeration('idle', ['idle', 'working']), + uiIsProcessing: types.maybe(types.boolean), uiError: types.maybe(types.string), // TODO: these should be stored somewhere secret @@ -14,56 +15,84 @@ export const SessionModel = types secretKeyStr: types.maybe(types.string), rootAuthToken: types.maybe(types.string), }) - .views(self => ({ - get hasAccount() { - return self.serverUrl && self.secretKeyStr && self.rootAuthToken - }, - })) .extend(withEnvironment) .actions(self => ({ setAuthed: (v: boolean) => { self.isAuthed = v }, - loadAccount: flow(function* () { - if (!self.hasAccount) { + login: flow(function* () { + self.uiIsProcessing = true + self.uiError = undefined + try { + if (!self.environment.authStore) { + throw new Error('Auth store not initialized') + } + const res = yield auth.requestAppUcan(self.environment.authStore) + self.isAuthed = res + self.uiIsProcessing = false + return res + } catch (e: any) { + console.error('Failed to request app ucan', e) + self.uiError = e.toString() + self.uiIsProcessing = false return false } - self.uiState = 'working' + }), + logout: flow(function* () { + self.uiIsProcessing = true self.uiError = undefined try { - const cfg = yield UserConfig.hydrate({ - serverUrl: self.serverUrl, - secretKeyStr: self.secretKeyStr, - rootAuthToken: self.rootAuthToken, - }) - self.environment.api.setUserCfg(cfg) + if (!self.environment.authStore) { + throw new Error('Auth store not initialized') + } + const res = yield auth.logout(self.environment.authStore) + self.isAuthed = false + self.uiIsProcessing = false + return res + } catch (e: any) { + console.error('Failed to log out', e) + self.uiError = e.toString() + self.uiIsProcessing = false + return false + } + }), + /*loadAccount: flow(function* () { + self.uiIsProcessing = true + self.uiError = undefined + try { + // const cfg = yield UserConfig.hydrate({ + // serverUrl: self.serverUrl, + // secretKeyStr: self.secretKeyStr, + // rootAuthToken: self.rootAuthToken, + // }) + // self.environment.api.setUserCfg(cfg) self.isAuthed = true - self.uiState = 'idle' + self.uiIsProcessing = false return true } catch (e: any) { console.error('Failed to create test account', e) self.uiError = e.toString() - self.uiState = 'idle' + self.uiIsProcessing = false return false } }), - createTestAccount: flow(function* (serverUrl: string) { - self.uiState = 'working' + createTestAccount: flow(function* (_serverUrl: string) { + self.uiIsProcessing = true self.uiError = undefined try { - const cfg = yield UserConfig.createTest(serverUrl) - const state = yield cfg.serialize() - self.serverUrl = state.serverUrl - self.secretKeyStr = state.secretKeyStr - self.rootAuthToken = state.rootAuthToken + // const cfg = yield UserConfig.createTest(serverUrl) + // const state = yield cfg.serialize() + // self.serverUrl = state.serverUrl + // self.secretKeyStr = state.secretKeyStr + // self.rootAuthToken = state.rootAuthToken self.isAuthed = true - self.environment.api.setUserCfg(cfg) + // self.environment.api.setUserCfg(cfg) } catch (e: any) { console.error('Failed to create test account', e) self.uiError = e.toString() } - self.uiState = 'idle' - }), + self.uiIsProcessing = false + }),*/ })) export interface Session extends Instance<typeof SessionModel> {} |