Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

authstore #38

Merged
merged 44 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8dbafe3
authstore
sethvincent Oct 11, 2022
a307d00
sketch of authstore using datastore
sethvincent Oct 17, 2022
f659643
recursive capability statement validation
sethvincent Nov 1, 2022
9133e68
wip authstore improvements
sethvincent Nov 7, 2022
2675360
Merge branch 'main' into authstore
sethvincent Nov 14, 2022
fdc35e9
finish rewriting getCapabilities, fix various small issues, start uti…
sethvincent Nov 16, 2022
1a6f878
improve authstore capabilities and test helpers
sethvincent Nov 17, 2022
9b18ede
authstore ownership statements
sethvincent Nov 23, 2022
6b7a24d
authstore, datastore, datatype rewrite
sethvincent Dec 6, 2022
e0327ea
cleanup
sethvincent Dec 6, 2022
fb0650e
format
sethvincent Dec 6, 2022
9ca9521
jsdoc for authstore methods
sethvincent Dec 7, 2022
16b39c0
improved signatures
sethvincent Dec 9, 2022
4a26800
improve fetching statements by version, update deps
sethvincent Dec 9, 2022
09e5cf2
lint & type check passes
sethvincent Dec 9, 2022
a910e11
update readmes
sethvincent Dec 12, 2022
0735637
update deps
sethvincent Dec 12, 2022
ef7a773
update deps
sethvincent Dec 12, 2022
b3a402d
normalize types to use <name>Key for buffers and <name>Id for hex str…
sethvincent Dec 13, 2022
131e338
Merge branch 'authstore' of github.com:digidem/mapeo-core-next into a…
sethvincent Dec 13, 2022
c57b27e
update deps
sethvincent Dec 13, 2022
3084826
update package-lock.json
sethvincent Dec 13, 2022
87e7928
add timestamp to signatures
sethvincent Dec 13, 2022
29d6b52
update authstore readme, authstore role name changes
sethvincent Dec 19, 2022
82fb39f
move methods, small fixes
sethvincent Dec 20, 2022
346a717
format, move authstore methods, fix replicate test helper
sethvincent Dec 21, 2022
93ea624
fix tests
sethvincent Dec 22, 2022
ba2b914
remove namespace from authstore corestore
sethvincent Dec 22, 2022
493b62d
pass core to datatype instead of keypair, other small fixes
sethvincent Jan 9, 2023
ff345d1
use identityKeyPair as datastore keyPair
sethvincent Jan 9, 2023
f0e4cd0
use keypair public key as project public key for project creator
sethvincent Jan 10, 2023
d02b3ac
fix datatype test typo
sethvincent Jan 10, 2023
8d8fd1e
validate that links exist when updating
sethvincent Jan 10, 2023
fb370a0
add todo about validating dataType on update
sethvincent Jan 10, 2023
c96d684
format
sethvincent Jan 10, 2023
b5977a9
validate link datatype on update
sethvincent Jan 10, 2023
72eb753
chore: Add types for hyercore & corestore (#57)
gmaclennan Jan 10, 2023
8ca505b
update types
sethvincent Jan 10, 2023
a7e63b5
format
sethvincent Jan 10, 2023
3cc686f
fix replicate methods
sethvincent Jan 10, 2023
41df651
remove duplicate jsdoc param
sethvincent Jan 11, 2023
7b40efc
update schemas
sethvincent Jan 12, 2023
47766c5
types/jsdoc updates for authstore
sethvincent Jan 17, 2023
666b957
add exports
sethvincent Jan 17, 2023
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
1 change: 0 additions & 1 deletion example.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const corestore = new Corestore(ram)
const writer = corestore.get({ name: 'writer' })
await writer.ready()

// TODO: actual schema from mapeo-schema
const observation = new DataType({
name: 'observation',
blockPrefix: '6f62', // could make this automatically set based on the name, but that might be too magic
Expand Down
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MultiCoreIndexer from 'multi-core-indexer'
import b4a from 'b4a'
import ram from 'random-access-memory'

import { AuthStore } from './lib/authstore/index.js'
import { DataStore } from './lib/datastore/index.js'
import { Indexer } from './lib/indexer/index.js'
export { DataType } from './lib/datatype/index.js'
Expand Down Expand Up @@ -88,4 +89,10 @@ export class Mapeo {
return dataType.blockPrefix === typeHex
})
}

async sync(options) {}

async syncAuthStore(options) {}

async syncDataStores(options) {}
}
17 changes: 17 additions & 0 deletions lib/authstore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# AuthStore

> Identities and authorization.

## Purpose

The `AuthStore` class is responsible for defining user identities and authorizing their access to Mapeo projects.

## Usage

## API docs

TODO!

## Tests

Tests for this module are in [tests/authstore.js](../../tests/authstore.js)
73 changes: 73 additions & 0 deletions lib/authstore/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { DataStore } from './datastore.js'
import { DataType } from '../datatype.js'

const capabilities = new DataType({
name: 'capabilities',
blockPrefix: 'capa',
schema: {
additionalProperties: true, // TODO: actual schema
},
})

// TODO: we may need to override this
function getWinner(docA, docB) {
if (
// Checking neither null nor undefined
docA.timestamp != null &&
docB.timestamp != null
) {
if (docA.timestamp > docB.timestamp) return docA
if (docB.timestamp > docA.timestamp) return docB
}
// They are equal or no timestamp property, so sort by version to ensure winner is deterministic
return docA.version > docB.version ? docA : docB
}

// TODO: this may need its own sqlite-indexer and multi-core-indexer instances, particularly if we have multiple data types stored in this core
export class Authstore {
constructor(options) {
this.corestore = options.corestore
this.capabilities = new DataStore({
dataType: capabilities,
corestore: options.corestore,
indexer: options.indexer,
})
}

async ready() {
await this.capabilities.ready()
}

async create({ identityPublicKey, creator, capability }) {
sethvincent marked this conversation as resolved.
Show resolved Hide resolved
// TODO: validate that user can write new capabilities based on current capabilities
return this.capabilities.create({
sethvincent marked this conversation as resolved.
Show resolved Hide resolved
id: identityPublicKey,
creator,
capability,
})
}

async update({ identityPublicKey, creator, capability }) {
// TODO: validate that user can write new capabilities based on current capabilities
return this.capabilities.update({
id: identityPublicKey,
creator,
capability,
})
}

validateCapability({ identityPublicKey, capability }) {}
sethvincent marked this conversation as resolved.
Show resolved Hide resolved

getCapabilities(identityPublicKey) {
return this.capabilities.query(`identifyPublicKey == ${identityPublicKey}`)
sethvincent marked this conversation as resolved.
Show resolved Hide resolved
}

query(where) {
return this.capabilities.query(where)
}

async close() {
await this.corestore.close()
await this.capabilities.close()
}
}
3 changes: 2 additions & 1 deletion lib/datastore/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class DataStore {
const doc = Object.assign(data, {
id: data.id || randomBytes(8).toString('hex'),
version: `${this.#writer.key.toString('hex')}@${this.#writer.length}`,
created_at: new Date().toISOString(),
created_at: new Date().getTime(),
})

if (!doc.links) {
Expand Down Expand Up @@ -109,6 +109,7 @@ export class DataStore {
async update(data) {
const doc = Object.assign({}, data, {
version: `${this.#writer.key.toString('hex')}@${this.#writer.length}`,
updated_at: new Date().getTime(),
})

const indexing = new Promise((resolve) => {
Expand Down
3 changes: 2 additions & 1 deletion lib/indexer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class Indexer {
* @param {string} options.extraColumns any additional column definitions needed for this table, passed to `CREATE TABLE` statement
*/
constructor(options) {
const { dataType, sqlite, extraColumns } = options
const { dataType, sqlite, extraColumns, getWinner } = options

this.name = dataType.name
this.#sqlite = sqlite
Expand Down Expand Up @@ -48,6 +48,7 @@ export class Indexer {
docTableName: this.name,
backlinkTableName: `${this.name}_backlinks`,
extraColumns: this.extraColumns,
getWinner,
})
}

Expand Down
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
"hyperswarm": "^4.3.3",
"multi-core-indexer": "github:digidem/multi-core-indexer",
"multicast-service-discovery": "^4.0.3",
"mutexify": "^1.4.0",
sethvincent marked this conversation as resolved.
Show resolved Hide resolved
"sodium-native": "^3.4.1",
"sodium-universal": "^3.1.0",
"tiny-typed-emitter": "^2.1.0"
}
}
148 changes: 148 additions & 0 deletions tests/authstore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import test from 'brittle'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so far just writing out tests rather than using a strategy runner that takes json as input. the api was changing too much for that. i think it would be best to create an issue for adding a strategy runner later.

import { addCores, createAuthStore, replicate } from './helpers/index.js'

test('get capabilities and check access levels', async (t) => {
t.plan(6)

const auth1 = await createAuthStore()
const auth2 = await createAuthStore({
projectKeyPair: auth1.projectKeyPair,
})

replicate(auth1, auth2)
addCores(auth1, auth2)

await auth1.authstore.append({
type: 'capabilities',
capability: 'admin',
identityPublicKey: auth1.identityKeyPair.publicKey.toString('hex'),
})

await auth1.authstore.append({
type: 'capabilities',
capability: 'member',
identityPublicKey: auth2.identityKeyPair.publicKey.toString('hex'),
})

t.is(
(await auth1.authstore.getCapabilities()).length,
4,
'auth1 should have 2 capabilities statements'
)

t.is(
(await auth2.authstore.getCapabilities()).length,
4,
'auth2 should have 2 capabilities statements'
)

t.is(
await auth1.authstore.hasCapability({
capability: 'admin',
identityPublicKey: auth1.identityKeyPair.publicKey.toString('hex'),
}),
true,
'auth1 should have admin capability'
)

t.is(
await auth1.authstore.hasCapability({
capability: 'admin',
identityPublicKey: auth2.identityKeyPair.publicKey.toString('hex'),
}),
false,
'auth2 should not have admin capability'
)

t.is(
await auth2.authstore.hasCapability({
capability: 'admin',
identityPublicKey: auth1.identityKeyPair.publicKey.toString('hex'),
}),
true,
'auth1 should have admin capability'
)

t.is(
await auth2.authstore.hasCapability({
capability: 'admin',
identityPublicKey: auth2.identityKeyPair.publicKey.toString('hex'),
}),
false,
'auth2 should not have admin capability'
)
})

test('resolve fork at lowest capability', async (t) => {
t.plan(3)

const auth1 = await createAuthStore()
const auth2 = await createAuthStore({ projectKeyPair: auth1.projectKeyPair })
const auth3 = await createAuthStore({ projectKeyPair: auth1.projectKeyPair })

replicate(auth1, auth2)
replicate(auth1, auth3)
replicate(auth2, auth3)

auth1.authstore.addCore(auth2.authstore.key)
auth1.authstore.addCore(auth3.authstore.key)

auth2.authstore.addCore(auth1.authstore.key)
auth2.authstore.addCore(auth3.authstore.key)

auth3.authstore.addCore(auth2.authstore.key)
auth3.authstore.addCore(auth1.authstore.key)

await auth1.authstore.append({
type: 'capabilities',
capability: 'admin',
identityPublicKey: auth1.identityKeyPair.publicKey.toString('hex'),
})

await auth1.authstore.append({
type: 'capabilities',
capability: 'admin',
identityPublicKey: auth2.identityKeyPair.publicKey.toString('hex'),
})

await auth2.authstore.append({
type: 'capabilities',
capability: 'none',
identityPublicKey: auth3.authstore.identityPublicKeyString,
})

await auth1.authstore.append({
type: 'capabilities',
capability: 'member',
identityPublicKey: auth3.authstore.identityPublicKeyString,
})

const capabilities = await auth1.authstore.getCapabilities(
auth3.authstore.identityPublicKeyString
)

const auth2capabilities = await auth2.authstore.getCapabilities(
auth3.authstore.identityPublicKeyString
)

const isAdmin = await auth1.authstore.hasCapability({
capability: 'admin',
identityPublicKey: auth3.authstore.identityPublicKeyString,
})

t.is(isAdmin, false, 'auth3 should not be admin')

const isMember = await auth1.authstore.hasCapability({
capability: 'member',
identityPublicKey: auth3.authstore.identityPublicKeyString,
})

t.is(isMember, false, 'auth3 should not be member')

const isBanned = await auth1.authstore.hasCapability({
capability: 'none',
identityPublicKey: auth3.authstore.identityPublicKeyString,
})

t.is(isBanned, true, 'auth3 should have no access to the project')
})
Loading