Skip to content

Commit

Permalink
Send project name to server when adding it
Browse files Browse the repository at this point in the history
Closes [#912].

[#912]: #912
  • Loading branch information
EvanHahn committed Oct 29, 2024
1 parent 1c4f1ed commit 50a5271
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 52 deletions.
8 changes: 8 additions & 0 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ export class MapeoProject extends TypedEmitter {
roles: this.#roles,
coreOwnership: this.#coreOwnership,
encryptionKeys,
getProjectName: this.#getProjectName.bind(this),
projectKey,
rpc: localPeers,
getReplicationStream,
Expand Down Expand Up @@ -609,6 +610,13 @@ export class MapeoProject extends TypedEmitter {
}
}

/**
* @returns {Promise<undefined | string>}
*/
async #getProjectName() {
return (await this.$getProjectSettings()).name
}

async $getOwnRole() {
return this.#roles.getRole(this.#deviceId)
}
Expand Down
16 changes: 16 additions & 0 deletions src/member-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { wsCoreReplicator } from './server/ws-core-replicator.js'
* ProjectSettingsValue
* } from '@comapeo/schema'
*/
/** @import { Promisable } from 'type-fest' */
/** @import { Invite, InviteResponse } from './generated/rpc.js' */
/** @import { DataType } from './datatype/index.js' */
/** @import { DataStore } from './datastore/index.js' */
Expand All @@ -52,6 +53,7 @@ export class MemberApi extends TypedEmitter {
#roles
#coreOwnership
#encryptionKeys
#getProjectName
#projectKey
#rpc
#getReplicationStream
Expand All @@ -67,6 +69,7 @@ export class MemberApi extends TypedEmitter {
* @param {import('./roles.js').Roles} opts.roles
* @param {import('./core-ownership.js').CoreOwnership} opts.coreOwnership
* @param {import('./generated/keys.js').EncryptionKeys} opts.encryptionKeys
* @param {() => Promisable<undefined | string>} opts.getProjectName
* @param {Buffer} opts.projectKey
* @param {import('./local-peers.js').LocalPeers} opts.rpc
* @param {() => ReplicationStream} opts.getReplicationStream
Expand All @@ -80,6 +83,7 @@ export class MemberApi extends TypedEmitter {
roles,
coreOwnership,
encryptionKeys,
getProjectName,
projectKey,
rpc,
getReplicationStream,
Expand All @@ -91,6 +95,7 @@ export class MemberApi extends TypedEmitter {
this.#roles = roles
this.#coreOwnership = coreOwnership
this.#encryptionKeys = encryptionKeys
this.#getProjectName = getProjectName
this.#projectKey = projectKey
this.#rpc = rpc
this.#getReplicationStream = getReplicationStream
Expand Down Expand Up @@ -268,6 +273,8 @@ export class MemberApi extends TypedEmitter {
* Can reject with any of the following error codes (accessed via `err.code`):
*
* - `INVALID_URL`: the base URL is invalid, likely due to user error.
* - `MISSING_DATA`: some required data is missing in order to add the server
* peer. For example, the project must have a name.
* - `NETWORK_ERROR`: there was an issue connecting to the server. Is the
* device online? Is the server online?
* - `INVALID_SERVER_RESPONSE`: we connected to the server but it returned
Expand Down Expand Up @@ -308,8 +315,17 @@ export class MemberApi extends TypedEmitter {
* @returns {Promise<{ serverDeviceId: string }>}
*/
async #addServerToProject(baseUrl) {
const projectName = await this.#getProjectName()
if (!projectName) {
throw new ErrorWithCode(
'MISSING_DATA',
'Project must have name to add server peer'
)
}

const requestUrl = new URL('projects', baseUrl)
const requestBody = {
projectName,
projectKey: encodeBufferForServer(this.#projectKey),
encryptionKeys: {
auth: encodeBufferForServer(this.#encryptionKeys.auth),
Expand Down
4 changes: 3 additions & 1 deletion src/server/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export default async function routes(
{
schema: {
body: Type.Object({
projectName: Type.String(),
projectKey: HEX_STRING_32_BYTES,
encryptionKeys: Type.Object({
auth: HEX_STRING_32_BYTES,
Expand All @@ -153,6 +154,7 @@ export default async function routes(
},
},
async function (req, reply) {
const { projectName } = req.body
const projectKey = Buffer.from(req.body.projectKey, 'hex')
const projectPublicId = projectKeyToPublicId(projectKey)

Expand Down Expand Up @@ -207,7 +209,7 @@ export default async function routes(
const projectId = await this.comapeo.addProject(
{
projectKey,
projectName: 'TODO: Figure out if this should be named',
projectName,
encryptionKeys: {
auth: Buffer.from(req.body.encryptionKeys.auth, 'hex'),
config: Buffer.from(req.body.encryptionKeys.config, 'hex'),
Expand Down
60 changes: 42 additions & 18 deletions src/server/test/add-project-endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,39 @@ import assert from 'node:assert/strict'
import test from 'node:test'
import { omit } from '../../lib/omit.js'
import { projectKeyToPublicId } from '../../utils.js'
import { createTestServer, randomProjectKeys } from './test-helpers.js'
import { createTestServer, randomAddProjectBody } from './test-helpers.js'

test('request missing project name', async (t) => {
const server = createTestServer(t)

const response = await server.inject({
method: 'PUT',
url: '/projects',
body: omit(randomAddProjectBody(), ['projectName']),
})

assert.equal(response.statusCode, 400)
})

test('request with empty project name', async (t) => {
const server = createTestServer(t)

const response = await server.inject({
method: 'PUT',
url: '/projects',
body: { ...randomAddProjectBody(), projectName: '' },
})

assert.equal(response.statusCode, 400)
})

test('request missing project key', async (t) => {
const server = createTestServer(t)

const response = await server.inject({
method: 'PUT',
url: '/projects',
body: omit(randomProjectKeys(), ['projectKey']),
body: omit(randomAddProjectBody(), ['projectKey']),
})

assert.equal(response.statusCode, 400)
Expand All @@ -22,22 +46,22 @@ test('request missing any encryption keys', async (t) => {
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: omit(randomProjectKeys(), ['encryptionKeys']),
body: omit(randomAddProjectBody(), ['encryptionKeys']),
})

assert.equal(response.statusCode, 400)
})

test('request missing an encryption key', async (t) => {
const server = createTestServer(t)
const projectKeys = randomProjectKeys()
const body = randomAddProjectBody()

const response = await server.inject({
method: 'PUT',
url: '/projects',
body: {
...projectKeys,
encryptionKeys: omit(projectKeys.encryptionKeys, ['config']),
...body,
encryptionKeys: omit(body.encryptionKeys, ['config']),
},
})

Expand All @@ -50,7 +74,7 @@ test('adding a project', async (t) => {
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: randomProjectKeys(),
body: randomAddProjectBody(),
})

assert.equal(response.statusCode, 200)
Expand All @@ -65,14 +89,14 @@ test('adding a second project fails by default', async (t) => {
const firstAddResponse = await server.inject({
method: 'PUT',
url: '/projects',
body: randomProjectKeys(),
body: randomAddProjectBody(),
})
assert.equal(firstAddResponse.statusCode, 200)

const response = await server.inject({
method: 'PUT',
url: '/projects',
body: randomProjectKeys(),
body: randomAddProjectBody(),
})
assert.equal(response.statusCode, 403)
assert.match(response.json().message, /maximum number of projects/)
Expand All @@ -86,7 +110,7 @@ test('allowing a maximum number of projects', async (t) => {
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: randomProjectKeys(),
body: randomAddProjectBody(),
})
assert.equal(response.statusCode, 200)
}
Expand All @@ -96,7 +120,7 @@ test('allowing a maximum number of projects', async (t) => {
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: randomProjectKeys(),
body: randomAddProjectBody(),
})
assert.equal(response.statusCode, 403)
assert.match(response.json().message, /maximum number of projects/)
Expand All @@ -107,9 +131,9 @@ test(
'allowing a specific list of projects',
{ concurrency: true },
async (t) => {
const projectKeys = randomProjectKeys()
const body = randomAddProjectBody()
const projectPublicId = projectKeyToPublicId(
Buffer.from(projectKeys.projectKey, 'hex')
Buffer.from(body.projectKey, 'hex')
)
const server = createTestServer(t, {
allowedProjects: [projectPublicId],
Expand All @@ -119,7 +143,7 @@ test(
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: projectKeys,
body,
})
assert.equal(response.statusCode, 200)
})
Expand All @@ -128,7 +152,7 @@ test(
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: randomProjectKeys(),
body: randomAddProjectBody(),
})
assert.equal(response.statusCode, 403)
})
Expand All @@ -137,19 +161,19 @@ test(

test('adding the same project twice is idempotent', async (t) => {
const server = createTestServer(t, { allowedProjects: 1 })
const projectKeys = randomProjectKeys()
const body = randomAddProjectBody()

const firstResponse = await server.inject({
method: 'PUT',
url: '/projects',
body: projectKeys,
body,
})
assert.equal(firstResponse.statusCode, 200)

const secondResponse = await server.inject({
method: 'PUT',
url: '/projects',
body: projectKeys,
body,
})
assert.equal(secondResponse.statusCode, 200)
})
26 changes: 16 additions & 10 deletions src/server/test/list-projects-endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import test from 'node:test'
import {
BEARER_TOKEN,
createTestServer,
randomProjectKeys,
randomAddProjectBody,
} from './test-helpers.js'
import { projectKeyToPublicId } from '../../utils.js'

Expand All @@ -30,15 +30,15 @@ test('listing projects', async (t) => {
})

await t.test('with projects', async () => {
const projectKeys1 = randomProjectKeys()
const projectKeys2 = randomProjectKeys()
const body1 = randomAddProjectBody()
const body2 = randomAddProjectBody()

await Promise.all(
[projectKeys1, projectKeys2].map(async (projectKeys) => {
[body1, body2].map(async (body) => {
const response = await server.inject({
method: 'PUT',
url: '/projects',
body: projectKeys,
body,
})
assert.equal(response.statusCode, 200)
})
Expand All @@ -54,13 +54,19 @@ test('listing projects', async (t) => {
const { data } = response.json()
assert(Array.isArray(data))
assert.equal(data.length, 2, 'expected 2 projects')
for (const projectKeys of [projectKeys1, projectKeys2]) {
for (const body of [body1, body2]) {
const projectPublicId = projectKeyToPublicId(
Buffer.from(projectKeys.projectKey, 'hex')
Buffer.from(body.projectKey, 'hex')
)
assert(
data.some((project) => project.projectId === projectPublicId),
`expected ${projectPublicId} to be found`
/** @type {Record<string, unknown>} */
const project = data.find(
(project) => project.projectId === projectPublicId
)
assert(project, `expected ${projectPublicId} to be found`)
assert.equal(
project.name,
body.projectName,
'expected project name to match'
)
}
})
Expand Down
8 changes: 4 additions & 4 deletions src/server/test/observations-endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { createManager } from '../../../test-e2e/utils.js'
import {
BEARER_TOKEN,
createTestServer,
randomProjectKeys,
randomAddProjectBody,
} from './test-helpers.js'
/** @import { ObservationValue } from '@comapeo/schema'*/
/** @import { FastifyInstance } from 'fastify' */
Expand Down Expand Up @@ -44,7 +44,7 @@ test('returns a 403 if incorrect auth is provided', async (t) => {

test('returning no observations', async (t) => {
const server = createTestServer(t)
const projectKeys = randomProjectKeys()
const projectKeys = randomAddProjectBody()
const projectPublicId = projectKeyToPublicId(
Buffer.from(projectKeys.projectKey, 'hex')
)
Expand Down Expand Up @@ -72,7 +72,7 @@ test('returning observations with fetchable attachments', async (t) => {
const serverUrl = new URL(serverAddress)

const manager = createManager('client', t)
const projectId = await manager.createProject()
const projectId = await manager.createProject({ name: 'CoMapeo project' })
const project = await manager.getProject(projectId)

await project.$member.addServerPeer(serverAddress, {
Expand Down Expand Up @@ -161,7 +161,7 @@ test('returning observations with fetchable attachments', async (t) => {

function randomProjectPublicId() {
return projectKeyToPublicId(
Buffer.from(randomProjectKeys().projectKey, 'hex')
Buffer.from(randomAddProjectBody().projectKey, 'hex')
)
}

Expand Down
8 changes: 4 additions & 4 deletions src/server/test/sync-endpoint.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { projectKeyToPublicId } from '../../utils.js'
import { createTestServer, randomProjectKeys } from './test-helpers.js'
import { createTestServer, randomAddProjectBody } from './test-helpers.js'

test('sync endpoint is available after adding a project', async (t) => {
const server = createTestServer(t)
const projectKeys = randomProjectKeys()
const addProjectBody = randomAddProjectBody()
const projectPublicId = projectKeyToPublicId(
Buffer.from(projectKeys.projectKey, 'hex')
Buffer.from(addProjectBody.projectKey, 'hex')
)

await t.test('sync endpoint not available yet', async () => {
Expand All @@ -26,7 +26,7 @@ test('sync endpoint is available after adding a project', async (t) => {
await server.inject({
method: 'PUT',
url: '/projects',
body: projectKeys,
body: addProjectBody,
})

await t.test('sync endpoint available', async (t) => {
Expand Down
Loading

0 comments on commit 50a5271

Please sign in to comment.