about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-06-14 14:29:47 -0500
committerPaul Frazee <pfrazee@gmail.com>2022-06-14 14:29:47 -0500
commitcef133031e501f8f73c66a379de38b1041287743 (patch)
treeb628150cbee4facbcbfc06599baaf4c108949995 /src
parent09b78a46343088a2790dab067bd4fd8384957311 (diff)
downloadvoidsky-cef133031e501f8f73c66a379de38b1041287743.tar.zst
Add base auth & ucan request flow (web only)
Diffstat (limited to 'src')
-rw-r--r--src/api/auth.ts48
-rw-r--r--src/api/index.ts37
-rw-r--r--src/env.ts5
-rw-r--r--src/screens/Home.tsx2
-rw-r--r--src/screens/Login.tsx9
-rw-r--r--src/screens/Signup.tsx6
-rw-r--r--src/state/env.ts6
-rw-r--r--src/state/index.ts12
-rw-r--r--src/state/models/session.ts85
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> {}