Implement signin flow

This commit is contained in:
Paul Frazee 2022-09-26 21:03:07 -05:00
parent 2e352f383e
commit 0208302907
19 changed files with 652 additions and 300 deletions

View file

@ -8,13 +8,15 @@ import * as storage from './lib/storage'
import {ShellModel} from './models/shell'
const ROOT_STATE_STORAGE_KEY = 'root'
const DEFAULT_SERVICE = 'http://localhost:2583'
export async function setupState() {
let rootStore: RootStoreModel
let data: any
const api = AdxApi.service(`http://localhost:2583`)
await libapi.setup(api)
libapi.doPolyfill()
const api = AdxApi.service(DEFAULT_SERVICE)
rootStore = new RootStoreModel(api)
try {
data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {}
@ -29,17 +31,7 @@ export async function setupState() {
storage.save(ROOT_STATE_STORAGE_KEY, snapshot)
})
// TODO
rootStore.session.setAuthed(true)
// if (env.authStore) {
// const isAuthed = await auth.isAuthed(env.authStore)
// rootStore.session.setAuthed(isAuthed)
// // handle redirect from auth
// if (await auth.initialLoadUcanCheck(env.authStore)) {
// rootStore.session.setAuthed(true)
// }
// }
await rootStore.session.setup()
await rootStore.me.load()
console.log(rootStore.me)

View file

@ -3,13 +3,13 @@
* models live. They are made available to every model via dependency injection.
*/
import RNFetchBlob from 'rn-fetch-blob'
// import {ReactNativeStore} from './auth'
import AdxApi, {ServiceClient} from '../../third-party/api'
import AdxApi from '../../third-party/api'
import {ServiceClient} from '../../third-party/api/src/index'
import {AdxUri} from '../../third-party/uri'
import * as storage from './storage'
export async function setup(adx: ServiceClient) {
export function doPolyfill() {
AdxApi.xrpc.fetch = fetchHandler
}
@ -121,32 +121,31 @@ async function fetchHandler(
reqHeaders: Record<string, string>,
reqBody: any,
): Promise<FetchHandlerResponse> {
reqHeaders['Authorization'] = 'did:test:alice' // DEBUG
const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
if (reqMimeType && reqMimeType.startsWith('application/json')) {
reqBody = JSON.stringify(reqBody)
}
const res = await RNFetchBlob.fetch(
/** @ts-ignore method coersion, it's fine -prf */
reqMethod,
reqUri,
reqHeaders,
reqBody,
)
const res = await fetch(reqUri, {
method: reqMethod,
headers: reqHeaders,
body: reqBody,
})
const resStatus = res.info().status
const resHeaders = (res.info().headers || {}) as Record<string, string>
const resStatus = res.status
const resHeaders: Record<string, string> = {}
res.headers.forEach((value: string, key: string) => {
resHeaders[key] = value
})
const resMimeType = resHeaders['Content-Type'] || resHeaders['content-type']
let resBody
if (resMimeType) {
if (resMimeType.startsWith('application/json')) {
resBody = res.json()
resBody = await res.json()
} else if (resMimeType.startsWith('text/')) {
resBody = res.text()
resBody = await res.text()
} else {
resBody = res.base64()
throw new Error('TODO: non-textual response body')
}
}
return {

View file

@ -14,9 +14,8 @@ export class MeModel {
async load() {
const sess = this.rootStore.session
if (sess.isAuthed) {
// TODO
this.did = 'did:test:alice'
this.name = 'alice.todo'
this.did = sess.userdid || ''
this.name = sess.username
const profile = await this.rootStore.api.todo.social.getProfile({
user: this.did,
})

View file

@ -4,7 +4,7 @@
import {makeAutoObservable} from 'mobx'
import AdxApi from '../../third-party/api'
import {ServiceClient} from '../../third-party/api/src/index'
import type {ServiceClient} from '../../third-party/api/src/index'
import {createContext, useContext} from 'react'
import {isObj, hasProp} from '../lib/type-guards'
import {SessionModel} from './session'
@ -13,7 +13,7 @@ import {ShellModel} from './shell'
import {MeModel} from './me'
export class RootStoreModel {
session = new SessionModel()
session = new SessionModel(this)
nav = new NavigationModel()
shell = new ShellModel()
me = new MeModel(this)

View file

@ -1,109 +1,133 @@
import {makeAutoObservable} from 'mobx'
import AdxApi from '../../third-party/api'
import {isObj, hasProp} from '../lib/type-guards'
// import {UserConfig} from '../../api'
// import * as auth from '../lib/auth'
import {RootStoreModel} from './root-store'
interface SessionData {
service: string
token: string
username: string
userdid: string
}
export class SessionModel {
isAuthed = false
data: SessionData | null = null
constructor() {
constructor(public rootStore: RootStoreModel) {
makeAutoObservable(this, {
rootStore: false,
serialize: false,
hydrate: false,
})
}
get isAuthed() {
return this.data !== null
}
serialize(): unknown {
return {
isAuthed: this.isAuthed,
}
return this.data
}
hydrate(v: unknown) {
if (isObj(v)) {
if (hasProp(v, 'isAuthed') && typeof v.isAuthed === 'boolean') {
this.isAuthed = v.isAuthed
const data: SessionData = {
service: '',
token: '',
username: '',
userdid: '',
}
if (hasProp(v, 'service') && typeof v.service === 'string') {
data.service = v.service
}
if (hasProp(v, 'token') && typeof v.token === 'string') {
data.token = v.token
}
if (hasProp(v, 'username') && typeof v.username === 'string') {
data.username = v.username
}
if (hasProp(v, 'userdid') && typeof v.userdid === 'string') {
data.userdid = v.userdid
}
if (data.service && data.token && data.username && data.userdid) {
this.data = data
}
}
}
setAuthed(v: boolean) {
this.isAuthed = v
clear() {
console.log('clear()')
this.data = null
}
setState(data: SessionData) {
this.data = data
}
private configureApi(): boolean {
if (!this.data) {
return false
}
try {
const serviceUri = new URL(this.data.service)
this.rootStore.api.xrpc.uri = serviceUri
} catch (e) {
console.error(
`Invalid service URL: ${this.data.service}. Resetting session.`,
)
console.error(e)
this.clear()
return false
}
this.rootStore.api.setHeader('Authorization', `Bearer ${this.data.token}`)
return true
}
async setup(): Promise<void> {
if (!this.configureApi()) {
return
}
try {
const sess = await this.rootStore.api.todo.adx.getSession({})
if (sess.success && this.data && this.data.userdid === sess.data.did) {
return // success
}
} catch (e: any) {}
this.clear() // invalid session cached
}
async login({
service,
username,
password,
}: {
service: string
username: string
password: string
}) {
const api = AdxApi.service(service)
const res = await api.todo.adx.createSession({}, {username, password})
if (res.data.jwt) {
this.setState({
service: service,
token: res.data.jwt,
username: res.data.name,
userdid: res.data.did,
})
this.configureApi()
}
}
async logout() {
if (this.isAuthed) {
this.rootStore.api.todo.adx.deleteSession({}).catch((e: any) => {
console.error('(Minor issue) Failed to delete session on the server', e)
})
}
this.clear()
}
}
// TODO
/*login: flow(function* () {
/*self.uiIsProcessing = true
self.uiError = undefined
try {
if (!self.env.authStore) {
throw new Error('Auth store not initialized')
}
const res = yield auth.requestAppUcan(self.env.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
}
}),
logout: flow(function* () {
self.uiIsProcessing = true
self.uiError = undefined
try {
if (!self.env.authStore) {
throw new Error('Auth store not initialized')
}
const res = yield auth.logout(self.env.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.env.api.setUserCfg(cfg)
self.isAuthed = true
self.uiIsProcessing = false
return true
} catch (e: any) {
console.error('Failed to create test account', e)
self.uiError = e.toString()
self.uiIsProcessing = false
return false
}
}),
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
self.isAuthed = true
// self.env.api.setUserCfg(cfg)
} catch (e: any) {
console.error('Failed to create test account', e)
self.uiError = e.toString()
}
self.uiIsProcessing = false
}),
}))*/