6
6
Aes128Key ,
7
7
Aes256Key ,
8
8
aes256RandomKey ,
9
+ aesDecrypt ,
9
10
aesEncrypt ,
10
11
AesKey ,
11
12
decryptKey ,
@@ -14,14 +15,15 @@ import {
14
15
unauthenticatedAesDecrypt ,
15
16
} from "@tutao/tutanota-crypto"
16
17
import { UserFacade } from "../UserFacade.js"
17
- import { EncryptedDbKeyBaseMetaData , EncryptedIndexerMetaData , Metadata , ObjectStoreName } from "../../search/IndexTables.js"
18
+ import { EncryptedDbKeyBaseMetaData , EncryptedIndexerMetaData , LocalDraftDataOS , Metadata , ObjectStoreName } from "../../search/IndexTables.js"
18
19
import { DbError } from "../../../common/error/DbError.js"
19
20
import { checkKeyVersionConstraints , KeyLoaderFacade } from "../KeyLoaderFacade.js"
20
- import type { QueuedBatch } from "../../EventQueue.js"
21
21
import { _encryptKeyWithVersionedKey , VersionedKey } from "../../crypto/CryptoWrapper.js"
22
22
import { EntityUpdateData , isUpdateForTypeRef } from "../../../common/utils/EntityUpdateUtils"
23
+ import * as cborg from "cborg"
24
+ import { customTypeDecoders , customTypeEncoders } from "../../offline/OfflineStorage"
23
25
24
- const VERSION : number = 2
26
+ const VERSION : number = 3
25
27
const DB_KEY_PREFIX : string = "ConfigStorage"
26
28
const ExternalImageListOS : ObjectStoreName = "ExternalAllowListOS"
27
29
export const ConfigurationMetaDataOS : ObjectStoreName = "MetaDataOS"
@@ -43,6 +45,26 @@ export async function decryptLegacyItem(encryptedAddress: Uint8Array, key: Aes25
43
45
return utf8Uint8ArrayToString ( unauthenticatedAesDecrypt ( key , concat ( iv , encryptedAddress ) ) )
44
46
}
45
47
48
+ const LOCAL_DRAFT_VERSION : number = 1
49
+ export type LocalDraftAddress = {
50
+ name : string
51
+ address : string
52
+ }
53
+ export type LocalDraftData = {
54
+ version : number
55
+
56
+ mailId : IdTuple | null
57
+ mailGroupId : Id
58
+
59
+ subject : string
60
+ body : string
61
+
62
+ senderAddress : string
63
+ to : LocalDraftAddress [ ]
64
+ cc : LocalDraftAddress [ ]
65
+ bcc : LocalDraftAddress [ ]
66
+ }
67
+
46
68
/**
47
69
* A local configuration database that can be used as an alternative to DeviceConfig:
48
70
* Ideal for cases where the configuration values should be stored encrypted,
@@ -65,6 +87,66 @@ export class ConfigurationDatabase {
65
87
} )
66
88
}
67
89
90
+ async setDraftData ( draftUpdateDataWithoutVersion : Omit < LocalDraftData , "version" > ) : Promise < void > {
91
+ const { db, metaData } = await this . db . getAsync ( )
92
+ if ( ! db . indexingSupported ) return
93
+
94
+ const draftUpdateData : LocalDraftData = Object . assign ( { } , draftUpdateDataWithoutVersion , { version : LOCAL_DRAFT_VERSION } )
95
+
96
+ try {
97
+ const transaction = await db . createTransaction ( false , [ LocalDraftDataOS ] )
98
+ const encoded = cborg . encode ( draftUpdateData , { typeEncoders : customTypeEncoders } )
99
+ const encryptedData = aesEncrypt ( metaData . key , encoded , metaData . iv )
100
+ await transaction . put ( LocalDraftDataOS , "current" , encryptedData ) // FIXME
101
+ } catch ( e ) {
102
+ if ( e instanceof DbError ) {
103
+ console . error ( "failed to save draft:" , e . message )
104
+ return
105
+ }
106
+ throw e
107
+ }
108
+ }
109
+
110
+ async getDraftData ( ) : Promise < LocalDraftData | null > {
111
+ const { db, metaData } = await this . db . getAsync ( )
112
+ if ( ! db . indexingSupported ) return null
113
+
114
+ try {
115
+ const transaction = await db . createTransaction ( false , [ LocalDraftDataOS ] )
116
+ const data = await transaction . get < Uint8Array > ( LocalDraftDataOS , "current" ) // FIXME
117
+ if ( data == null ) {
118
+ return null
119
+ }
120
+
121
+ const decryptedData = aesDecrypt ( metaData . key , data )
122
+ const encoded = cborg . decode ( decryptedData , { tags : customTypeDecoders } )
123
+
124
+ return encoded as LocalDraftData
125
+ } catch ( e ) {
126
+ if ( e instanceof DbError ) {
127
+ console . error ( "failed to load draft:" , e . message )
128
+ return null
129
+ }
130
+ throw e
131
+ }
132
+ }
133
+
134
+ async clearDraftData ( ) : Promise < void > {
135
+ const { db } = await this . db . getAsync ( )
136
+ if ( ! db . indexingSupported ) return
137
+
138
+ try {
139
+ const transaction = await db . createTransaction ( false , [ LocalDraftDataOS ] )
140
+ await transaction . delete ( LocalDraftDataOS , "current" ) // FIXME
141
+ } catch ( e ) {
142
+ if ( e instanceof DbError ) {
143
+ console . error ( "failed to load draft:" , e . message )
144
+ return
145
+ }
146
+ throw e
147
+ }
148
+ }
149
+
68
150
async addExternalImageRule ( address : string , rule : ExternalImageRule ) : Promise < void > {
69
151
const { db, metaData } = await this . db . getAsync ( )
70
152
if ( ! db . indexingSupported ) return
@@ -96,11 +178,14 @@ export class ConfigurationDatabase {
96
178
async loadConfigDb ( user : User , keyLoaderFacade : KeyLoaderFacade ) : Promise < ConfigDb > {
97
179
const id = this . getDbId ( user . _id )
98
180
const db = new DbFacade ( VERSION , async ( event , db , dbFacade ) => {
181
+ console . log ( `MIGRATING DB FOR VERSION ${ event . oldVersion } ` )
182
+
99
183
if ( event . oldVersion === 0 ) {
100
184
db . createObjectStore ( ConfigurationMetaDataOS )
101
185
db . createObjectStore ( ExternalImageListOS , {
102
186
keyPath : "address" ,
103
187
} )
188
+ db . createObjectStore ( LocalDraftDataOS )
104
189
}
105
190
const metaData =
106
191
( await loadEncryptionMetadata ( dbFacade , id , keyLoaderFacade , ConfigurationMetaDataOS ) ) ||
@@ -118,7 +203,12 @@ export class ConfigurationDatabase {
118
203
await deleteTransaction . delete ( ExternalImageListOS , entry . key )
119
204
}
120
205
}
206
+
207
+ if ( event . oldVersion < 3 ) {
208
+ db . createObjectStore ( LocalDraftDataOS )
209
+ }
121
210
} )
211
+
122
212
const metaData =
123
213
( await loadEncryptionMetadata ( db , id , keyLoaderFacade , ConfigurationMetaDataOS ) ) ||
124
214
( await initializeDb ( db , id , keyLoaderFacade , ConfigurationMetaDataOS ) )
0 commit comments