文件
mynode/packages/lib/services/noteLock/NoteLockSession.ts
chen 301194963b
一些检查失败了
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
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

100 行
3.6 KiB
TypeScript

import NoteLockKey, { DecryptedNoteLockKey } from './NoteLockKey';
import eventManager, { EventName } from '../../eventManager';
import { noteLockKeyConflict } from '../synchronizer/syncInfoUtils';
import { _ } from '../../locale';
export default class NoteLockSession {
public static instance_: NoteLockSession = null;
private key_: DecryptedNoteLockKey = null;
private lockGeneration_ = 0;
private rotating_ = false;
private migrating_ = false;
private constructor(private noteLockKey_: NoteLockKey = NoteLockKey.instance()) {}
public static instance() {
if (!this.instance_) {
this.instance_ = new NoteLockSession();
}
return this.instance_;
}
public static destroyInstance() {
// lock() bumps the generation so a mid-decrypt unlock() cannot repopulate a torn-down session.
this.instance_?.lock();
this.instance_ = null;
}
// Re-checks state after the await so a lock, reset, synced change or teardown that raced in
// can't install a stale key. A reset that starts mid-decrypt also locks, so the generation
// check covers it.
public async unlock(password: string) {
if (this.rotating_) throw new Error('Cannot unlock: a note lock key reset is in progress');
if (this.migrating_) throw new Error(_('This note cannot be read while a note lock key migration is in progress.'));
const generation = this.lockGeneration_;
const decrypted = await this.noteLockKey_.decrypt(password);
if (this.lockGeneration_ !== generation) throw new Error('Cannot unlock: the session was locked while unlocking');
if (this.noteLockKey_.load()?.id !== decrypted.id) throw new Error('Cannot unlock: the note lock key changed while unlocking');
this.key_ = decrypted;
eventManager.emit(EventName.NoteLockSessionChange, { unlocked: true });
}
public lock() {
this.lockGeneration_++;
const wasUnlocked = !!this.key_;
this.key_ = null;
if (wasUnlocked) eventManager.emit(EventName.NoteLockSessionChange, { unlocked: false });
}
// Blocks unlock during rotation so the still-persisted old key can't be unlocked before the new one is saved.
public async reset(password: string) {
if (this.rotating_) throw new Error('A note lock key reset is already in progress');
// The migration would adopt the target key over the new one when it ends.
if (this.migrating_) throw new Error(_('The note lock password cannot be reset while a note lock key migration is in progress.'));
this.rotating_ = true;
this.lock();
try {
return await this.noteLockKey_.reset(password);
} finally {
this.lock();
this.rotating_ = false;
}
}
// Locked notes stay closed in every window while a migration run re-encrypts them.
public setMigrating(migrating: boolean) {
this.migrating_ = migrating;
if (migrating) this.lock();
}
public isMigrating() {
return this.migrating_;
}
private lockIfKeyChanged_() {
if (this.key_ && this.key_.id !== this.noteLockKey_.load()?.id) this.lock();
}
public isUnlocked() {
this.lockIfKeyChanged_();
return !!this.key_;
}
public decryptedKey(): DecryptedNoteLockKey {
this.lockIfKeyChanged_();
if (!this.key_) throw new Error('Note lock session is locked');
return {
id: this.key_.id,
plainText: this.key_.plainText,
};
}
// A lock clears key_ but not the persisted key, so this only fails on a real rotation, or one in progress.
// The key parked for migration is allowed too: migration re-encrypts to it before it is adopted.
public assertCanEncryptWith(keyId: string) {
if (this.rotating_ || (this.noteLockKey_.load()?.id !== keyId && noteLockKeyConflict()?.noteLockKey.id !== keyId)) throw new Error('Note lock key changed during operation');
}
}