wip save ok
This commit is contained in:
@@ -330,7 +330,15 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
"attachments" field for access control.
|
||||
"""
|
||||
content = self.validated_data.get("content", "")
|
||||
extracted_attachments = set(utils.extract_attachments(content))
|
||||
|
||||
# Encrypted content cannot be parsed as a Yjs update
|
||||
# TODO: for now skip attachment extraction for encrypted documents but we should have them
|
||||
is_encrypted = self.validated_data.get(
|
||||
"is_encrypted", self.instance and self.instance.is_encrypted
|
||||
)
|
||||
extracted_attachments = (
|
||||
set() if is_encrypted else set(utils.extract_attachments(content))
|
||||
)
|
||||
|
||||
existing_attachments = (
|
||||
set(self.instance.attachments or []) if self.instance else set()
|
||||
@@ -418,15 +426,15 @@ class DocumentAccessSerializer(serializers.ModelSerializer):
|
||||
def get_fields(self):
|
||||
"""Dynamically control field availability and requirements based on document encryption status."""
|
||||
fields = super().get_fields()
|
||||
|
||||
|
||||
# Get the document from context (if available)
|
||||
document = None
|
||||
if "view" in self.context and hasattr(self.context["view"], "document"):
|
||||
document = self.context["view"].document
|
||||
|
||||
|
||||
# Get the encrypted_document_symmetric_key_for_user field
|
||||
key_field = fields.get("encrypted_document_symmetric_key_for_user")
|
||||
|
||||
|
||||
if key_field:
|
||||
# If document is encrypted, make the field required
|
||||
if document and getattr(document, "is_encrypted", False):
|
||||
@@ -435,7 +443,7 @@ class DocumentAccessSerializer(serializers.ModelSerializer):
|
||||
# If document is not encrypted, remove the field entirely
|
||||
elif document and not getattr(document, "is_encrypted", False):
|
||||
fields.pop("encrypted_document_symmetric_key_for_user", None)
|
||||
|
||||
|
||||
return fields
|
||||
|
||||
def get_abilities(self, instance) -> dict:
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
export class EncryptedWebSocket extends WebSocket {
|
||||
protected readonly encryptionKey!: CryptoKey;
|
||||
protected readonly decryptionKey!: CryptoKey;
|
||||
protected readonly onSystemMessage?: (message: string) => void;
|
||||
|
||||
constructor(address: string | URL, protocols?: string | string[]) {
|
||||
super(address, protocols);
|
||||
@@ -24,6 +25,15 @@ export class EncryptedWebSocket extends WebSocket {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const messageEvent = event as any;
|
||||
|
||||
// some messages are here to help adjusting the interface or even reloading it
|
||||
// in case it there is an ongoing decryption, or symmetric key rotation...
|
||||
// those messages must be parsable so they are not encrypted
|
||||
if (typeof messageEvent.data === 'string') {
|
||||
this.onSystemMessage?.(messageEvent.data as string);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!(messageEvent.data instanceof ArrayBuffer)) {
|
||||
throw new Error(
|
||||
@@ -127,9 +137,11 @@ export class EncryptedWebSocket extends WebSocket {
|
||||
export function createAdaptedEncryptedWebsocketClass(options: {
|
||||
encryptionKey: CryptoKey;
|
||||
decryptionKey: CryptoKey;
|
||||
onSystemMessage?: (message: string) => void;
|
||||
}) {
|
||||
return class extends EncryptedWebSocket {
|
||||
protected readonly encryptionKey = options.encryptionKey;
|
||||
protected readonly decryptionKey = options.decryptionKey;
|
||||
protected readonly onSystemMessage = options.onSystemMessage;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isFirefox } from '@/utils/userAgent';
|
||||
|
||||
import { toBase64 } from '../utils';
|
||||
|
||||
const SAVE_INTERVAL = 100 * 60000;
|
||||
const SAVE_INTERVAL = 60000;
|
||||
|
||||
export const useSaveDoc = (
|
||||
docId: string,
|
||||
|
||||
@@ -56,6 +56,11 @@ export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
||||
const AdaptedEncryptedWebSocket = createAdaptedEncryptedWebsocketClass({
|
||||
encryptionKey: encryptionSymmetricKey,
|
||||
decryptionKey: encryptionSymmetricKey,
|
||||
onSystemMessage: (message) => {
|
||||
if (message === 'system:authenticated') {
|
||||
set({ isReady: true, isConnected: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
provider = new RelayProvider(wsUrl, storeId, doc, {
|
||||
|
||||
@@ -84,6 +84,9 @@ export const collaborationWSHandler = async (
|
||||
// Since for "end-to-end encryption" the server cannot maintains its own state for the document
|
||||
// we use a different strategy with a relay server
|
||||
if (document.is_encrypted) {
|
||||
// mimick the Hocuspocus protocol to properly hide the frontend loader
|
||||
ws.send('system:authenticated');
|
||||
|
||||
await handleRelayServerConnection(ws, roomId);
|
||||
} else {
|
||||
hocuspocusServer.hocuspocus.handleConnection(ws, req, {
|
||||
|
||||
Reference in New Issue
Block a user