feat(web): add account workspaces and signed content flows
This commit is contained in:
1 parent
7b8d61d1ba
commit
b5cf3d5958
117 files changed
+2789
-1674
No files matched your search
@@ -78,9 +78,12 @@ beforeAll(async () => {
|
||||
const verifier = crypto.getRandomValues(new Uint8Array(32));
|
||||
const keys = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits']);
|
||||
const pubkey = await crypto.subtle.exportKey('jwk', keys.publicKey);
|
||||
const signingKeys = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']);
|
||||
const signingPublicKey = await crypto.subtle.exportKey('jwk', signingKeys.publicKey);
|
||||
const encoded = (value: Uint8Array) => Buffer.from(value).toString('base64');
|
||||
const registered = await fetch(`${base}/api/v1/accounts`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
|
||||
username: 'core_e2e', salt: encoded(crypto.getRandomValues(new Uint8Array(16))), verifier: encoded(verifier), vault: encoded(new Uint8Array(28)), public_key: JSON.stringify(pubkey),
|
||||
username: 'core_e2e', salt: encoded(crypto.getRandomValues(new Uint8Array(16))), verifier: encoded(verifier), vault: encoded(new Uint8Array(28)),
|
||||
public_key: JSON.stringify(pubkey), signing_public_key: JSON.stringify(signingPublicKey),
|
||||
}) });
|
||||
expect(registered.status).toBe(204);
|
||||
const session = await fetch(`${base}/api/v1/accounts/session`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'core_e2e', verifier: encoded(verifier) }) });
|
||||
@@ -98,7 +101,7 @@ afterAll(async () => {
|
||||
const api = (path: string) => `${base}/api/v1${path}`;
|
||||
|
||||
async function createChannel(opts: Parameters<typeof ChannelSession.create>[0] = {}) {
|
||||
const created = await ChannelSession.create(opts);
|
||||
const created = await ChannelSession.create({ ...opts, settings: { name: 'Core E2E', ...opts.settings } });
|
||||
const res = await fetch(api('/channels'), {
|
||||
method: 'POST',
|
||||
headers: { ...created.request.headers, 'X-WD-Account': accountToken, 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { fromBase64Url } from '../src/bytes.js';
|
||||
import { contentEventPayload, frameManifestHash, generateSigningKeyPair, signStatement, verifyStatement } from '../src/signatures.js';
|
||||
import { h, vectors } from './helpers.js';
|
||||
|
||||
describe('协议签名原语', () => {
|
||||
it('跨语言固定向量可以验签,且类型和负载均受签名保护', async () => {
|
||||
const fixture = vectors.signatures;
|
||||
const publicKey = await crypto.subtle.importKey('jwk', {
|
||||
kty: 'EC', crv: 'P-256', x: fixture.x, y: fixture.y, ext: true,
|
||||
}, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
||||
const payload = h(fixture.payload);
|
||||
const signature = h(fixture.signature);
|
||||
expect(await verifyStatement(publicKey, fixture.kind, payload, signature)).toBe(true);
|
||||
expect(await verifyStatement(publicKey, 'chat.edit', payload, signature)).toBe(false);
|
||||
payload[0] ^= 1;
|
||||
expect(await verifyStatement(publicKey, fixture.kind, payload, signature)).toBe(false);
|
||||
expect(await verifyStatement(publicKey, fixture.kind, h(fixture.payload), signature.subarray(1))).toBe(false);
|
||||
expect(fromBase64Url(fixture.x)).toHaveLength(32);
|
||||
});
|
||||
|
||||
it('浏览器签名可验证,篡改被拒绝', async () => {
|
||||
const pair = await generateSigningKeyPair();
|
||||
expect(pair.privateKey.extractable).toBe(false);
|
||||
const payload = new TextEncoder().encode('signed command bytes');
|
||||
const signature = await signStatement(pair.privateKey, 'workspace.channel.create', payload);
|
||||
expect(signature).toHaveLength(64);
|
||||
expect(await verifyStatement(pair.publicKey, 'workspace.channel.create', payload, signature)).toBe(true);
|
||||
expect(await verifyStatement(pair.publicKey, 'workspace.group.create', payload, signature)).toBe(false);
|
||||
await expect(signStatement(pair.privateKey, 'Workspace.Channel.Create', payload)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('聊天/文件签名上下文与帧清单跨语言向量一致', async () => {
|
||||
const event = vectors.content_event;
|
||||
const manifest = await frameManifestHash(event.frame_digests.map((digest: string) => h(digest)));
|
||||
expect(manifest).toEqual(h(event.manifest_hash));
|
||||
expect(contentEventPayload({
|
||||
spaceId: h(event.space_id), channelId: event.channel_id, username: event.username, deviceId: event.device_id,
|
||||
eventId: h(event.event_id), eventKind: event.kind, domain: event.domain, epoch: event.epoch,
|
||||
itemId: h(event.item_id), frameCount: event.frame_count, manifestHash: manifest, targetItemId: h(event.target_item_id),
|
||||
})).toEqual(h(event.payload));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user