diff --git a/src/encrypt.ts b/src/encrypt.ts index 19f815c..6c6d5ea 100644 --- a/src/encrypt.ts +++ b/src/encrypt.ts @@ -8,6 +8,9 @@ import { const DEFAULT_ITER = 10000; +// base32.stringify(Buffer.from('Salted__')) +export const MAGIC_ENCRYPTED_PREFIX_BASE32 = "KNQWY5DFMRPV"; + const getKeyIVFromPassword = async ( salt: Uint8Array, password: string, diff --git a/src/main.ts b/src/main.ts index 639b82a..c2e4685 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,8 +26,8 @@ import { insertSyncPlanRecord, } from "./localdb"; -import type { SyncStatusType } from "./sync"; -import { getSyncPlan, doActualSync } from "./sync"; +import type { SyncStatusType, PasswordCheckType } from "./sync"; +import { isPasswordOk, getSyncPlan, doActualSync } from "./sync"; import { DEFAULT_S3_CONFIG, getS3Client, @@ -98,7 +98,17 @@ export default class SaveRemotePlugin extends Plugin { // console.log(local); // console.log(localHistory); - new Notice("4/6 Starting to generate sync plan."); + new Notice("4/7 Checking password correct or not."); + this.syncStatus = "checking_password"; + const passwordCheckResult = await isPasswordOk( + remoteRsp.Contents, + this.settings.password + ); + if (!passwordCheckResult.ok) { + throw Error(passwordCheckResult.reason); + } + + new Notice("5/7 Starting to generate sync plan."); this.syncStatus = "generating_plan"; const syncPlan = await getSyncPlan( remoteRsp.Contents, @@ -113,7 +123,7 @@ export default class SaveRemotePlugin extends Plugin { // The operations above are read only and kind of safe. // The operations below begins to write or delete (!!!) something. - new Notice("5/6 Save Remote Sync data exchanging!"); + new Notice("6/7 Save Remote Sync data exchanging!"); this.syncStatus = "syncing"; await doActualSync( @@ -125,7 +135,7 @@ export default class SaveRemotePlugin extends Plugin { this.settings.password ); - new Notice("6/6 Save Remote finish!"); + new Notice("7/7 Save Remote finish!"); this.syncStatus = "finish"; this.syncStatus = "idle"; } catch (error) { @@ -133,7 +143,7 @@ export default class SaveRemotePlugin extends Plugin { console.log(msg); console.log(error); new Notice(msg); - new Notice(error); + new Notice(error.message); this.syncStatus = "idle"; } }); diff --git a/src/sync.ts b/src/sync.ts index ff28937..8507d71 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -17,13 +17,18 @@ import { downloadFromRemote, } from "./s3"; import { mkdirpInVault, SUPPORTED_SERVICES_TYPE, isHiddenPath } from "./misc"; -import { decryptBase32ToString, encryptStringToBase32 } from "./encrypt"; +import { + decryptBase32ToString, + encryptStringToBase32, + MAGIC_ENCRYPTED_PREFIX_BASE32, +} from "./encrypt"; export type SyncStatusType = | "idle" | "preparing" | "getting_remote_meta" | "getting_local_meta" + | "checking_password" | "generating_plan" | "syncing" | "finish"; @@ -61,6 +66,66 @@ export interface SyncPlanType { mixedStates: Record; } +export interface PasswordCheckType { + ok: boolean; + reason: + | "ok" + | "empty_remote" + | "remote_encrypted_local_no_password" + | "password_matched" + | "password_not_matched" + | "remote_not_encrypted_local_has_password" + | "no_password_both_sides"; +} + +export const isPasswordOk = async ( + remote: S3ObjectType[], + password: string = "" +) => { + if (remote === undefined || remote.length === 0) { + // remote empty + return { + ok: true, + reason: "empty_remote", + } as PasswordCheckType; + } + const santyCheckKey = remote[0].Key; + if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) { + // this is encrypted! + // try to decrypt it using the provided password. + if (password === "") { + return { + ok: false, + reason: "remote_encrypted_local_no_password", + } as PasswordCheckType; + } + try { + const res = await decryptBase32ToString(santyCheckKey, password); + return { + ok: true, + reason: "password_matched", + } as PasswordCheckType; + } catch (error) { + return { + ok: false, + reason: "password_not_matched", + } as PasswordCheckType; + } + } else { + // it is not encrypted! + if (password !== "") { + return { + ok: false, + reason: "remote_not_encrypted_local_has_password", + } as PasswordCheckType; + } + return { + ok: true, + reason: "no_password_both_sides", + } as PasswordCheckType; + } +}; + const ensembleMixedStates = async ( remote: S3ObjectType[], local: TAbstractFile[],