♻️ backend: adding freeform instanceid to identify multiple instances of plugin (#525)
This commit is contained in:
@@ -135,6 +135,14 @@ const OnlyOfficeSafeDocKeyBase64 = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function checkFieldValue(field: string, value: string) {
|
||||||
|
if (!/^[0-9a-zA-Z_-]+$/m.test(value))
|
||||||
|
throw new Error(
|
||||||
|
`Invalid ${field} value (${JSON.stringify(
|
||||||
|
value,
|
||||||
|
)}). Must be short and only alpha numeric or '_' and '-'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
/** Reference implementation for generating then parsing the {@link DriveFile.editing_session_key} field */
|
/** Reference implementation for generating then parsing the {@link DriveFile.editing_session_key} field */
|
||||||
export const EditingSessionKeyFormat = {
|
export const EditingSessionKeyFormat = {
|
||||||
// OnlyOffice key limits: 128 chars, [0-9a-zA-Z.=_-]
|
// OnlyOffice key limits: 128 chars, [0-9a-zA-Z.=_-]
|
||||||
@@ -145,13 +153,15 @@ export const EditingSessionKeyFormat = {
|
|||||||
// common denominator to all plugin/interop systems. Plugins that
|
// common denominator to all plugin/interop systems. Plugins that
|
||||||
// require something even stricter have the option of maintaining
|
// require something even stricter have the option of maintaining
|
||||||
// a look up table to an acceptable value.
|
// a look up table to an acceptable value.
|
||||||
generate(applicationId: string, companyId: string, userId: string, overrideTimeStamp?: Date) {
|
generate(
|
||||||
if (!/^[0-9a-zA-Z_-]+$/m.test(applicationId))
|
applicationId: string,
|
||||||
throw new Error(
|
instanceId: string,
|
||||||
`Invalid applicationId string (${JSON.stringify(
|
companyId: string,
|
||||||
applicationId,
|
userId: string,
|
||||||
)}). Must be short and only alpha numeric`,
|
overrideTimeStamp?: Date,
|
||||||
);
|
) {
|
||||||
|
checkFieldValue("applicationId", applicationId);
|
||||||
|
checkFieldValue("instanceId", instanceId);
|
||||||
const isoUTCDateNoSpecialCharsNoMS = (overrideTimeStamp ?? new Date())
|
const isoUTCDateNoSpecialCharsNoMS = (overrideTimeStamp ?? new Date())
|
||||||
.toISOString()
|
.toISOString()
|
||||||
.replace(/\..+$/, "")
|
.replace(/\..+$/, "")
|
||||||
@@ -162,7 +172,7 @@ export const EditingSessionKeyFormat = {
|
|||||||
const idsString = OnlyOfficeSafeDocKeyBase64.fromBuffer(
|
const idsString = OnlyOfficeSafeDocKeyBase64.fromBuffer(
|
||||||
Buffer.concat([companyIdBuffer, userIdBuffer, entropyBuffer]),
|
Buffer.concat([companyIdBuffer, userIdBuffer, entropyBuffer]),
|
||||||
);
|
);
|
||||||
const newKey = [isoUTCDateNoSpecialCharsNoMS, applicationId, idsString].join("=");
|
const newKey = [isoUTCDateNoSpecialCharsNoMS, applicationId, instanceId, idsString].join("=");
|
||||||
if (newKey.length > 128 || !/^[0-9a-zA-Z=_-]+$/m.test(newKey))
|
if (newKey.length > 128 || !/^[0-9a-zA-Z=_-]+$/m.test(newKey))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid generated editingSessionKey (${JSON.stringify(
|
`Invalid generated editingSessionKey (${JSON.stringify(
|
||||||
@@ -174,14 +184,14 @@ export const EditingSessionKeyFormat = {
|
|||||||
|
|
||||||
parse(editingSessionKey: string) {
|
parse(editingSessionKey: string) {
|
||||||
const parts = editingSessionKey.split("=");
|
const parts = editingSessionKey.split("=");
|
||||||
const expectedParts = 3;
|
const expectedParts = 4;
|
||||||
if (parts.length !== expectedParts)
|
if (parts.length !== expectedParts)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid editingSessionKey (${JSON.stringify(
|
`Invalid editingSessionKey (${JSON.stringify(
|
||||||
editingSessionKey,
|
editingSessionKey,
|
||||||
)}). Expected ${expectedParts} parts`,
|
)}). Expected ${expectedParts} parts`,
|
||||||
);
|
);
|
||||||
const [timestampStr, appId, idsOOBase64String] = parts;
|
const [timestampStr, applicationId, instanceId, idsOOBase64String] = parts;
|
||||||
const timestampMatch = timestampStr.match(
|
const timestampMatch = timestampStr.match(
|
||||||
/^(?<year>\d{4})(?<month>\d\d)(?<day>\d\d)(?<hour>\d\d)(?<minute>\d\d)(?<second>\d\d)$/,
|
/^(?<year>\d{4})(?<month>\d\d)(?<day>\d\d)(?<hour>\d\d)(?<minute>\d\d)(?<second>\d\d)$/,
|
||||||
);
|
);
|
||||||
@@ -199,7 +209,8 @@ export const EditingSessionKeyFormat = {
|
|||||||
timestamp: new Date(
|
timestamp: new Date(
|
||||||
Date.parse(`${[year, month, day].join("-")}T${[hour, minute, second].join(":")}Z`),
|
Date.parse(`${[year, month, day].join("-")}T${[hour, minute, second].join(":")}Z`),
|
||||||
),
|
),
|
||||||
applicationId: appId,
|
applicationId,
|
||||||
|
instanceId,
|
||||||
companyId,
|
companyId,
|
||||||
userId,
|
userId,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -967,6 +967,7 @@ export class DocumentsService {
|
|||||||
beginEditing = async (
|
beginEditing = async (
|
||||||
id: string,
|
id: string,
|
||||||
editorApplicationId: string,
|
editorApplicationId: string,
|
||||||
|
appInstanceId: string,
|
||||||
context: DriveExecutionContext,
|
context: DriveExecutionContext,
|
||||||
) => {
|
) => {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
@@ -978,6 +979,7 @@ export class DocumentsService {
|
|||||||
try {
|
try {
|
||||||
newKey = EditingSessionKeyFormat.generate(
|
newKey = EditingSessionKeyFormat.generate(
|
||||||
editorApplicationId,
|
editorApplicationId,
|
||||||
|
appInstanceId,
|
||||||
context.company.id,
|
context.company.id,
|
||||||
context.user.id,
|
context.user.id,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ export class DocumentsController {
|
|||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Params: ItemRequestParams;
|
Params: ItemRequestParams;
|
||||||
//TODO application id should be received from the token that we have during the login
|
//TODO application id should be received from the token that we have during the login
|
||||||
Body: { editorApplicationId: string };
|
Body: { editorApplicationId: string; instanceId: string };
|
||||||
}>,
|
}>,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@@ -366,6 +366,7 @@ export class DocumentsController {
|
|||||||
return await globalResolver.services.documents.documents.beginEditing(
|
return await globalResolver.services.documents.documents.beginEditing(
|
||||||
id,
|
id,
|
||||||
request.body.editorApplicationId,
|
request.body.editorApplicationId,
|
||||||
|
request.body.instanceId || "",
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+8
-6
@@ -7,6 +7,7 @@ describe('DriveFile EditingSessionKeyFormat', () => {
|
|||||||
const mockAppId = 'tdrive_random_application_id';
|
const mockAppId = 'tdrive_random_application_id';
|
||||||
const mockCompanyId = randomUUID();
|
const mockCompanyId = randomUUID();
|
||||||
const mockUserId = randomUUID();
|
const mockUserId = randomUUID();
|
||||||
|
const mockInstanceId = "super-instance-id";
|
||||||
const mockTimestamp = new Date();
|
const mockTimestamp = new Date();
|
||||||
const mockTimestampWith0MS = mockTimestamp.getTime() - (mockTimestamp.getTime() % 1000);
|
const mockTimestampWith0MS = mockTimestamp.getTime() - (mockTimestamp.getTime() % 1000);
|
||||||
|
|
||||||
@@ -23,31 +24,32 @@ describe('DriveFile EditingSessionKeyFormat', () => {
|
|||||||
test('generates a valid value that can be parsed', async () => {
|
test('generates a valid value that can be parsed', async () => {
|
||||||
checkIsUUID(mockUserId);
|
checkIsUUID(mockUserId);
|
||||||
checkIsUUID(mockCompanyId);
|
checkIsUUID(mockCompanyId);
|
||||||
const key = EditingSessionKeyFormat.generate(mockAppId, mockCompanyId, mockUserId, mockTimestamp);
|
const key = EditingSessionKeyFormat.generate(mockAppId, mockInstanceId, mockCompanyId, mockUserId, mockTimestamp);
|
||||||
checkKeyIsOOCompatible(key);
|
checkKeyIsOOCompatible(key);
|
||||||
const parsed = EditingSessionKeyFormat.parse(key);
|
const parsed = EditingSessionKeyFormat.parse(key);
|
||||||
expect(parsed.applicationId).toBe(mockAppId);
|
expect(parsed.applicationId).toBe(mockAppId);
|
||||||
|
expect(parsed.instanceId).toBe(mockInstanceId);
|
||||||
expect(parsed.userId).toBe(mockUserId);
|
expect(parsed.userId).toBe(mockUserId);
|
||||||
expect(parsed.companyId).toBe(mockCompanyId);
|
expect(parsed.companyId).toBe(mockCompanyId);
|
||||||
expect(parsed.timestamp.getTime()).toBe(mockTimestampWith0MS);
|
expect(parsed.timestamp.getTime()).toBe(mockTimestampWith0MS);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates unique values', async () => {
|
test('generates unique values', async () => {
|
||||||
const key = EditingSessionKeyFormat.generate(mockAppId, mockCompanyId, mockUserId, mockTimestamp);
|
const key = EditingSessionKeyFormat.generate(mockAppId, mockInstanceId, mockCompanyId, mockUserId, mockTimestamp);
|
||||||
const key2 = EditingSessionKeyFormat.generate(mockAppId, mockCompanyId, mockUserId, mockTimestamp);
|
const key2 = EditingSessionKeyFormat.generate(mockAppId, mockInstanceId, mockCompanyId, mockUserId, mockTimestamp);
|
||||||
expect(key).not.toBe(key2);
|
expect(key).not.toBe(key2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks the appId', async () => {
|
test('checks the appId', async () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
EditingSessionKeyFormat.generate('invalid app id !', mockCompanyId, mockUserId);
|
EditingSessionKeyFormat.generate('invalid app id !', mockInstanceId, mockCompanyId, mockUserId);
|
||||||
}).toThrow('Invalid applicationId string');
|
}).toThrow('Invalid applicationId value');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks final length', async () => {
|
test('checks final length', async () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
const tooLongAppID = new Array(100).join('x');
|
const tooLongAppID = new Array(100).join('x');
|
||||||
EditingSessionKeyFormat.generate(tooLongAppID, mockCompanyId, mockUserId);
|
EditingSessionKeyFormat.generate(tooLongAppID, mockInstanceId, mockCompanyId, mockUserId);
|
||||||
}).toThrow('Must be <128 chars,');
|
}).toThrow('Must be <128 chars,');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user