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
@@ -3,6 +3,7 @@ import { concat, equalBytes, tryDecodeUtf8, toBase64Url, type Bytes } from './by
|
||||
import { IntegrityError, type IntegrityReason } from './errors.js';
|
||||
import { openFrame, parseEnvelope, type Envelope } from './frame.js';
|
||||
import { sha256 } from './kdf.js';
|
||||
import { frameManifestHash } from './signatures.js';
|
||||
import { decodeFirstFrame, type ItemMeta } from './item.js';
|
||||
import { parseLogRecord, type LogRecord, type RemovedReason } from './log.js';
|
||||
|
||||
@@ -29,6 +30,8 @@ export interface ReceivedItem {
|
||||
complete: boolean;
|
||||
/** 到第一个缺口为止已收到的内容。大文件请改用 decryptItemStream,不要在这里缓冲。 */
|
||||
content: Bytes;
|
||||
/** SHA-256 manifest over the complete ciphertext envelopes in frame-index order. */
|
||||
ciphertextManifestHash?: Bytes;
|
||||
}
|
||||
|
||||
export type ItemEvent =
|
||||
@@ -104,7 +107,7 @@ export class ItemReader {
|
||||
|
||||
const itemId = toBase64Url(env.itemId);
|
||||
try {
|
||||
return await this.#accept(env, itemId, seq);
|
||||
return await this.#accept(env, itemId, seq, Uint8Array.from(envelope));
|
||||
} catch (e) {
|
||||
if (!(e instanceof IntegrityError)) throw e;
|
||||
this.#open.delete(itemId);
|
||||
@@ -118,7 +121,7 @@ export class ItemReader {
|
||||
this.#done.delete(itemId);
|
||||
}
|
||||
|
||||
async #accept(env: Envelope, itemId: string, seq: number | undefined): Promise<ItemEvent[]> {
|
||||
async #accept(env: Envelope, itemId: string, seq: number | undefined, envelope: Bytes): Promise<ItemEvent[]> {
|
||||
const isEncFrame = (env.flags & Flags.Enc) !== 0;
|
||||
// 降级防御:enc 会话只接受认证过的 enc 帧,服务端不能注入明文帧;raw 会话反之
|
||||
if (isEncFrame !== (this.#ctx.mode === 'enc')) throw new IntegrityError('wrong_mode');
|
||||
@@ -139,7 +142,7 @@ export class ItemReader {
|
||||
}
|
||||
|
||||
// 重复帧:内容必须逐字节相同,相同则视为重放,不产生事件
|
||||
const digest = await sha256(env.payload);
|
||||
const digest = await sha256(envelope);
|
||||
const dup = state?.frames.get(env.frameIndex);
|
||||
if (dup) {
|
||||
if (!equalBytes(dup.digest, digest)) throw new IntegrityError('duplicate_mismatch');
|
||||
@@ -186,6 +189,9 @@ export class ItemReader {
|
||||
content: concat(...parts),
|
||||
};
|
||||
if (state.meta) item.meta = state.meta;
|
||||
if (complete) item.ciphertextManifestHash = Uint8Array.from(await frameManifestHash(
|
||||
Array.from({ length: state.finalIndex! + 1 }, (_, index) => state!.frames.get(index)!.digest),
|
||||
));
|
||||
|
||||
if (complete) {
|
||||
this.#open.delete(itemId);
|
||||
|
||||
Reference in new issue
Block a user