Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Adds model config to validate usernames #44

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Radiks-js Changelog

## 0.3.0-beta.1 - July 23, 2019

- Includes a static `validateUsername` property to model. If `true`, then the username is included in the model attributes. See [#44](https://github.com/blockstack-radiks/radiks/pull/44)

## 0.2.1 - June 9th, 2019

Thanks to @pradel for contributing most of the work in this release!
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ A client-side framework for building model-driven decentralized applications on
- [Saving a model](#saving-a-model)
- [Deleting a model](#deleting-a-model)
- [Querying models](#querying-models)
- [Counting models](#counting-models)
- [Fetching models created by the current user](#fetching-models-created-by-the-current-user)
- [Managing relational data](#managing-relational-data)
- [Extending the user model](#extending-the-user-model)
- [Validating usernames](#validating-usernames)
- [Collaboration](#collaboration)
- [UserGroup Model](#usergroup-model)
- [General Workflow](#general-workflow)
Expand Down Expand Up @@ -412,6 +414,21 @@ class MyAppUserModel extends User {
}
~~~

### Validating usernames

There are many situations where it is important to validate usernames that are associated with models in your app. For example, imagine a social network, where each post is tied to a specific Blockstack ID. You need to have some validation to ensure that the user who created that model is the same user who owns that Blockstack ID. Radiks can do this for you, as long as you add a special flag to your model, called `validateUsername`.

~~~javascript
class ModelWithUsername extends Model {
static validateUsername = true
static schema = { ... }
}
~~~

If you include this special `validateUsername` flag, then Radiks will pass on the necessar information to Radiks-server. Radiks-server will then perform some validation to ensure that model was indeed created by the username that they claim to have.

One important thing to note here is that this configuration requires you to use the `publish-data` scope when logging in with Blockstack.

## Collaboration

A key feature of Radiks is support for private collaboration between multiple users. Supporting collaboration with client-side encryption and user-owned storage can be complicated, but the patterns to implement it are generally the same for different apps. Radiks provides out-of-the box for collaboration, making it easy to build private, collaborative apps.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "radiks",
"version": "0.2.1",
"version": "0.3.0-beta.1",
"description": "Manage models with encryption and decentralized storage",
"main": "lib/index.js",
"author": "Hank Stoever",
Expand Down
15 changes: 15 additions & 0 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default class Model {
public static defaults: any = {};
public static className?: string;
public static emitter?: EventEmitter;
public static validateUsername = false;
schema: Schema;
_id: string;
attrs: Attrs;
Expand Down Expand Up @@ -127,6 +128,7 @@ export default class Model {
const now = new Date().getTime();
this.attrs.createdAt = this.attrs.createdAt || now;
this.attrs.updatedAt = now;
await this.setUsername();
await this.sign();
const encrypted = await this.encrypted();
const gaiaURL = await this.saveFile(encrypted);
Expand Down Expand Up @@ -163,6 +165,19 @@ export default class Model {
return path;
}

setUsername() {
const { validateUsername } = this.constructor as typeof Model;
if (validateUsername) {
const userSession = requireUserSession();
const { username, gaiaHubConfig } = userSession.loadUserData();
this.attrs.username = username;
// eslint-disable-next-line @typescript-eslint/camelcase
const { url_prefix, address } = gaiaHubConfig;
// eslint-disable-next-line @typescript-eslint/camelcase
this.attrs.gaiaURL = `${url_prefix}${address}/${this.blockstackPath()}`;
}
}

async fetch({ decrypt = true } = {}): Promise<this | undefined> {
const query = {
_id: this._id,
Expand Down
11 changes: 11 additions & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ export const fakeModel = () => {
});
return model;
};

export class ModelWithUsername extends Model {
static validateUsername = true;

static schema = {
message: {
type: String,
decrypted: true,
},
}
}
13 changes: 12 additions & 1 deletion test/model.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import './mocks/crypto';
import './setup';
import { verifyECDSA } from 'blockstack/lib/encryption';
import { fakeModel, TestModel } from './helpers';
import { fakeModel, TestModel, ModelWithUsername } from './helpers';
import User from '../src/models/user';
import SigningKey from '../src/models/signing-key';
import { getConfig } from '../src/config';

test('encrypts data', async (t) => {
// crypto.getRandomValues(16);
Expand Down Expand Up @@ -90,3 +91,13 @@ test('it return null if model not found', async () => {
const modelFindOne = await TestModel.findOne({ _id: 'notfound' });
expect(modelFindOne).toBe(undefined);
});

test.only('it includes username if validateUsername', async () => {
const user = await User.createWithCurrentUser();
const model = new ModelWithUsername({ message: 'hello' });
await model.save();
expect(model.attrs.username).toEqual(user.attrs.username);
expect(model.attrs.gaiaURL).not.toBeFalsy();
const gaiaURL = `https://gaia.blockstack.org/hub/1Me2Zi84EioQJcwDg5Kjgy5YaXgqXjxJYS/ModelWithUsername/${model._id}`;
expect(model.attrs.gaiaURL).toEqual(gaiaURL);
});
4 changes: 4 additions & 0 deletions test/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ beforeEach(async () => {
userData: {
appPrivateKey,
username: faker.name.findName(),
gaiaHubConfig: {
url_prefix: 'https://gaia.blockstack.org/hub/', // eslint-disable-line @typescript-eslint/camelcase
address: '1Me2Zi84EioQJcwDg5Kjgy5YaXgqXjxJYS',
},
profile: {
// TODO
},
Expand Down