一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
101 行
3.5 KiB
TypeScript
101 行
3.5 KiB
TypeScript
import uuid from '../../uuid';
|
|
import Setting from '../../models/Setting';
|
|
import EncryptionService from '../e2ee/EncryptionService';
|
|
import { MasterKeyEntity } from '../e2ee/types';
|
|
import { localSyncInfo, saveLocalSyncInfo } from '../synchronizer/syncInfoUtils';
|
|
|
|
export interface DecryptedNoteLockKey {
|
|
id: string;
|
|
plainText: string;
|
|
}
|
|
|
|
// Importers that predate note lock skip this file, since they only read .md files.
|
|
export const noteLockKeyFileName = 'note_lock_key.json';
|
|
|
|
export default class NoteLockKey {
|
|
|
|
public static instance_: NoteLockKey = null;
|
|
|
|
private constructor(private encryptionService_: EncryptionService = EncryptionService.instance()) {}
|
|
|
|
public static instance() {
|
|
if (!this.instance_) {
|
|
this.instance_ = new NoteLockKey();
|
|
}
|
|
return this.instance_;
|
|
}
|
|
|
|
public static destroyInstance() {
|
|
this.instance_ = null;
|
|
}
|
|
|
|
public load(): MasterKeyEntity {
|
|
return localSyncInfo().noteLockKey;
|
|
}
|
|
|
|
public save(o: MasterKeyEntity): MasterKeyEntity {
|
|
const key = { ...o };
|
|
if (!key.id) {
|
|
key.id = uuid.create();
|
|
key.created_time = Date.now();
|
|
}
|
|
key.updated_time = Date.now();
|
|
|
|
const syncInfo = localSyncInfo();
|
|
syncInfo.noteLockKey = key;
|
|
// Only a key migration replaces the sync migration id, so a reset keeps the lineage it belongs to.
|
|
if (!syncInfo.syncMigrationId) syncInfo.syncMigrationId = uuid.create();
|
|
saveLocalSyncInfo(syncInfo);
|
|
|
|
return key;
|
|
}
|
|
|
|
public async create(password: string) {
|
|
if (this.load()) throw new Error('Note lock key already exists');
|
|
const key = await this.encryptionService_.generateMasterKey(password);
|
|
// A key may have arrived through sync while the new one was being generated - keep it,
|
|
// since notes may already be locked with it on another device.
|
|
if (this.load()) throw new Error('Note lock key already exists');
|
|
return this.save(key);
|
|
}
|
|
|
|
// Rotate through NoteLockSession.reset() rather than calling this directly, so the session locks
|
|
// and drops the old key as part of the rotation.
|
|
public async reset(password: string) {
|
|
const generated = await this.encryptionService_.generateMasterKey(password);
|
|
// Read once the key is ready, so a sync that finished meanwhile counts; a second reset before that sync keeps the first backup.
|
|
if (!Setting.value('noteLock.keyIdToReset')) Setting.setValue('noteLock.keyIdToReset', this.load()?.id ?? '');
|
|
const key = this.save(generated);
|
|
// Lets the next sync push the new key to the target instead of adopting the old one back.
|
|
Setting.setValue('noteLock.passwordReset', true);
|
|
return key;
|
|
}
|
|
|
|
public async changePassword(currentPassword: string, newPassword: string) {
|
|
const key = this.load();
|
|
if (!key) throw new Error('Note lock key has not been created');
|
|
return this.save(await this.encryptionService_.reencryptMasterKey(key, currentPassword, newPassword));
|
|
}
|
|
|
|
public needsUpgrade() {
|
|
const key = this.load();
|
|
return !!key && !!this.encryptionService_.masterKeysThatNeedUpgrading([key]).length;
|
|
}
|
|
|
|
public async upgrade(password: string) {
|
|
const key = this.load();
|
|
if (!key) throw new Error('Note lock key has not been created');
|
|
if (!this.needsUpgrade()) return key;
|
|
return this.changePassword(password, password);
|
|
}
|
|
|
|
public async decrypt(password: string, key: MasterKeyEntity = null): Promise<DecryptedNoteLockKey> {
|
|
if (!key) key = this.load();
|
|
if (!key) throw new Error('Note lock key has not been created');
|
|
if (!key.id) throw new Error('Note lock key does not have an ID');
|
|
|
|
const plainText = await this.encryptionService_.decryptMasterKeyContent(key, password);
|
|
return { id: key.id, plainText };
|
|
}
|
|
}
|