User Storage
Describes how GoodDAPP uses Gun p2p database, to store user owned data.
We chose Gun since it's a javascript based, decentralized (p2p) with built in encryption database.
Check out their website for more info
You can import the UserStorage instance anywhere in the code by doing
import userStorage from '/lib/gundb/UserStorage'
//you can make sure its initialized by waiting on 'ready'
await userStorage.ready
The constructor calls init(), which logs in the user into Gun.
The user's password+pass are generated deterministically by signing a message with one of his HD wallet private keys.
The profile holds information of the user (signed with Gun SEA, so only he can modify it) such as:
- Name
- Email
- Mobile
- Wallet Address
- Username
Each profile field is an object of type:
export type ProfileField = {
value: EncryptedField,
display: string,
privacy: FieldPrivacy
}
- value - is the SEA encrypted value, so only the user can read it.
- display - is the string displayed on the DAPP to other users
- privacy - is the privacy level of the field (masked, public, private)
Fields such email and mobile can be set to be public, private or masked, this is in order to let the user control what information he wishes to disclose. If they are public then users will be able to send funds to the user directly by simply typing his mobile, email or username in the app.
We keep the users' profiles indexed by email, mobile (in case they are public), wallet address and username. This enables to connect blockchain transactions to user profiles. Specifically it is used in the user feed and in the "Send" flow to enable directly sending GoodDollars by mobile, email and username.
https://github.com/GoodDollar/GoodDAPP/blob/472b22a24dafac154409c2579dbbfcf4cf4e9922/src/lib/gundb/UserStorage.js#L504-L544
/**
* Generates index by field if privacy is public, or empty index if it's not public
*
* @param {string} field - Profile attribute
* @param {string} value - Profile attribute value
* @param {string} privacy - (private | public | masked)
* @returns Gun result promise after index is generated
* @todo This is world writable so theoritically a malicious user could delete the indexes
* need to develop for gundb immutable keys to non first user
*/
async indexProfileField(field: string, value: string, privacy: FieldPrivacy): Promise<ACK> {
if (!UserStorage.indexableFields[field]) return Promise.resolve({ err: 'Not indexable field', ok: 0 })
const cleanValue = UserStorage.cleanFieldForIndex(field, value)
if (!cleanValue) return Promise.resolve({ err: 'Indexable field cannot be null or empty', ok: 0 })
const indexNode = gun.get(`users/by${field}`).get(cleanValue)
logger.debug('indexProfileField', { field, cleanValue, value, privacy, indexNode })
An issue with the index is that currently any user can overwrite any entry in the index, since nodes in GunDB are writable by everyone. We are working on an extension to GunDB to create append only nodes so an index key, once set by a user can not be changed by anyone else besides him
//returns [email protected]
let value:string = await userStorage.getProfileFieldValue('email')
//the gun way
let field:ProfileField = await userStorage.profile.get('email').then()
The feed holds all the blockchain transactions the user did but also other system and messaging events.
We keep 3 indexes for easy access and display purposes:
By ID | Fast access to event details Each events is encrypted | gun.user().get('feed').
get('byid').get(<eventId>) | FeedEvent |
By Date(daily) | Display sorted by time to user with a reasonable paging scheme | gun.user().get('feed') .get(<date granularity day>) | Array<[<datetime>,<eventId>] |
Events count by date | Helper for pager to fetch next X events | gun.user().get('feed') .get('index').get(<date granularity day>) | Number |
Indexes are updated once an event arrives in the method updateFeedEvent:
https://github.com/GoodDollar/GoodDAPP/blob/472b22a24dafac154409c2579dbbfcf4cf4e9922/src/lib/gundb/UserStorage.js#L770
The in memory index is updated on every change to 'index' by add a gundb listener in the method initFeed:
https://github.com/GoodDollar/GoodDAPP/blob/472b22a24dafac154409c2579dbbfcf4cf4e9922/src/lib/gundb/UserStorage.js#L339
Last modified 2yr ago