@@ -8,32 +8,45 @@ import { login } from '@main/services/organization/auth';
88import { getUseKeychainClaim } from '@main/services/localUser/claim' ;
99
1010import { createLogger } from '@main/modules/logger' ;
11- import { decrypt , encrypt , isLegacyBlob } from '@main/utils/crypto' ;
11+ import {
12+ decrypt ,
13+ encrypt ,
14+ isClearTextToken ,
15+ isLegacyBlob ,
16+ } from '@main/utils/crypto' ;
1217
1318const logger = createLogger ( 'main.organizationCredentials' ) ;
1419
1520/* Returns the organization that the user is connected to */
16- export const getOrganizationTokens = async ( user_id : string ) => {
21+ export const getOrganizationTokens = async ( user_id : string , decryptPassword : string | null ) => {
1722 const prisma = getPrismaClient ( ) ;
1823
1924 try {
2025 const orgs = await prisma . organizationCredentials . findMany ( {
2126 where : { user_id } ,
2227 select : {
28+ id : true ,
2329 organization_id : true ,
2430 jwtToken : true ,
2531 } ,
2632 } ) ;
33+ const result : { organization_id : string ; jwtToken : string | null } [ ] = [ ] ;
34+ for ( const o of orgs ) {
35+ result . push ( {
36+ organization_id : o . organization_id ,
37+ jwtToken : await decryptMigrateJwtToken ( o , decryptPassword ) ,
38+ } ) ;
39+ }
40+ return result ;
2741
28- return orgs || [ ] ;
2942 } catch ( error ) {
3043 logger . error ( 'Failed to get organization tokens' , { error } ) ;
3144 return [ ] ;
3245 }
3346} ;
3447
3548/* Returns the organizations that the user should sign into */
36- export const organizationsToSignIn = async ( user_id : string ) => {
49+ export const organizationsToSignIn = async ( user_id : string , decryptPassword : string | null ) => {
3750 const prisma = getPrismaClient ( ) ;
3851
3952 try {
@@ -47,7 +60,7 @@ export const organizationsToSignIn = async (user_id: string) => {
4760 const finalCredentials : typeof credentials = [ ] ;
4861
4962 for ( let i = 0 ; i < credentials . length ; i ++ ) {
50- if ( await organizationCredentialsInvalid ( credentials [ i ] ) )
63+ if ( await organizationCredentialsInvalid ( credentials [ i ] , decryptPassword ) )
5164 finalCredentials . push ( credentials [ i ] ) ;
5265 }
5366
@@ -59,7 +72,7 @@ export const organizationsToSignIn = async (user_id: string) => {
5972} ;
6073
6174/* Returns whether the user should sign in a specific organization */
62- export const shouldSignInOrganization = async ( user_id : string , organization_id : string ) => {
75+ export const shouldSignInOrganization = async ( user_id : string , organization_id : string , decryptPassword : string | null ) => {
6376 const prisma = getPrismaClient ( ) ;
6477
6578 try {
@@ -70,31 +83,31 @@ export const shouldSignInOrganization = async (user_id: string, organization_id:
7083 } ,
7184 } ) ;
7285
73- return await organizationCredentialsInvalid ( org ) ;
86+ return await organizationCredentialsInvalid ( org , decryptPassword ) ;
7487 } catch {
7588 return true ;
7689 }
7790} ;
7891
7992/* Returns the access token of a user for an organization */
80- export const getAccessToken = async ( serverUrl : string ) => {
93+ export const getAccessToken = async ( serverUrl : string , decryptPassword : string | null ) => {
8194 const prisma = getPrismaClient ( ) ;
8295
8396 try {
8497 const credentials = await prisma . organizationCredentials . findFirst ( {
8598 where : { organization : { serverUrl } } ,
8699 } ) ;
87100 if ( ! credentials ) return null ;
88- return credentials . jwtToken || null ;
101+ return await decryptMigrateJwtToken ( credentials , decryptPassword ) ;
89102 } catch ( error ) {
90103 logger . error ( 'Failed to get access token' , { error } ) ;
91104 return null ;
92105 }
93106} ;
94107
95108/* Returns the current user of an organization */
96- export const getCurrentUser = async ( organizationServerUrl : string ) => {
97- const token = await getAccessToken ( organizationServerUrl ) ;
109+ export const getCurrentUser = async ( organizationServerUrl : string , decryptPassword : string | null ) => {
110+ const token = await getAccessToken ( organizationServerUrl , decryptPassword ) ;
98111 if ( ! token ) return null ;
99112
100113 try {
@@ -105,6 +118,7 @@ export const getCurrentUser = async (organizationServerUrl: string) => {
105118 }
106119} ;
107120
121+
108122/* Returns credentials for organization */
109123export const getOrganizationCredentials = async (
110124 organization_id : string ,
@@ -120,11 +134,13 @@ export const getOrganizationCredentials = async (
120134
121135 if ( ! credentials ) return null ;
122136
123- const password = await decryptData ( credentials . password , decryptPassword , credentials . id ) ;
137+ const password = await decryptMigratePassword ( credentials , decryptPassword ) ;
138+ const jwtToken = await decryptMigrateJwtToken ( credentials , decryptPassword ) ;
124139
125140 return {
126141 ...credentials ,
127142 password,
143+ jwtToken,
128144 } ;
129145 } catch ( error ) {
130146 logger . error ( 'Failed to get organization credentials' , { error } ) ;
@@ -178,6 +194,7 @@ export const addOrganizationCredentials = async (
178194
179195 try {
180196 password = await encryptData ( password , encryptPassword ) ;
197+ jwtToken = await encryptData ( jwtToken , encryptPassword ) ;
181198
182199 await prisma . organizationCredentials . create ( {
183200 data : {
@@ -213,6 +230,10 @@ export const updateOrganizationCredentials = async (
213230 password = await encryptData ( password , encryptPassword ) ;
214231 }
215232
233+ if ( jwtToken && ! passwordIsEncrypted ) {
234+ jwtToken = await encryptData ( jwtToken , encryptPassword ) ;
235+ }
236+
216237 const credentials = await prisma . organizationCredentials . findFirst ( {
217238 where : { user_id, organization_id } ,
218239 } ) ;
@@ -258,7 +279,7 @@ export const deleteOrganizationCredentials = async (organization_id: string, use
258279export const tryAutoSignIn = async ( user_id : string , decryptPassword : string | null ) => {
259280 const prisma = getPrismaClient ( ) ;
260281
261- const invalidCredentials = await organizationsToSignIn ( user_id ) ;
282+ const invalidCredentials = await organizationsToSignIn ( user_id , decryptPassword ) ;
262283
263284 const failedLogins : Organization [ ] = [ ] ;
264285
@@ -267,7 +288,10 @@ export const tryAutoSignIn = async (user_id: string, decryptPassword: string | n
267288
268289 let password = '' ;
269290 try {
270- password = await decryptData ( invalidCredential . password , decryptPassword , invalidCredential . id ) ;
291+ password = await decryptMigratePassword (
292+ invalidCredential ,
293+ decryptPassword ,
294+ ) ;
271295 } catch {
272296 throw new Error ( 'Incorrect decryption password' ) ;
273297 }
@@ -278,10 +302,11 @@ export const tryAutoSignIn = async (user_id: string, decryptPassword: string | n
278302 invalidCredential . email ,
279303 password ,
280304 ) ;
305+ const encryptedAccessToken = await encryptData ( accessToken , decryptPassword ) ;
281306
282307 await prisma . organizationCredentials . update ( {
283308 where : { id : invalidCredential . id } ,
284- data : { jwtToken : accessToken } ,
309+ data : { jwtToken : encryptedAccessToken } ,
285310 } ) ;
286311 } catch {
287312 failedLogins . push ( invalidCredential . organization ) ;
@@ -348,8 +373,7 @@ async function encryptData(data: string, encryptPassword?: string | null) {
348373/* Decrypt data */
349374export async function decryptData (
350375 data : string ,
351- decryptPassword ?: string | null ,
352- credentialId ?: string ,
376+ decryptPassword : string | null ,
353377) {
354378 // if no data was stored (password cleared), just return empty string
355379 if ( data . length === 0 ) {
@@ -361,11 +385,32 @@ export async function decryptData(
361385 const buffer = Buffer . from ( data , 'base64' ) ;
362386 return safeStorage . decryptString ( buffer ) ;
363387 } else if ( decryptPassword ) {
364- const decrypted = await decrypt ( data , decryptPassword ) ;
365- if ( isLegacyBlob ( data ) && credentialId ) {
388+ return decrypt ( data , decryptPassword ) ;
389+ } else {
390+ throw new Error ( 'Password is required to decrypt sensitive' ) ;
391+ }
392+ }
393+
394+ /* Decrypt credentials password. Update its encryption if needed. */
395+ export async function decryptMigratePassword (
396+ credential : { id : string , password : string } ,
397+ decryptPassword : string | null ,
398+ ) {
399+ // if password was cleared, just return empty string
400+ if ( credential . password . length === 0 ) {
401+ return '' ;
402+ }
403+
404+ const useKeychain = await getUseKeychainClaim ( ) ;
405+ if ( useKeychain ) {
406+ const buffer = Buffer . from ( credential . password , 'base64' ) ;
407+ return safeStorage . decryptString ( buffer ) ;
408+ } else if ( decryptPassword ) {
409+ const decrypted = await decrypt ( credential . password , decryptPassword ) ;
410+ if ( isLegacyBlob ( credential . password ) ) {
366411 try {
367412 await getPrismaClient ( ) . organizationCredentials . update ( {
368- where : { id : credentialId } ,
413+ where : { id : credential . id } ,
369414 data : { password : await encrypt ( decrypted , decryptPassword ) } ,
370415 } ) ;
371416 } catch {
@@ -378,15 +423,42 @@ export async function decryptData(
378423 }
379424}
380425
426+ /* Decrypt credentials JWT token. Update its encryption if needed. */
427+ export async function decryptMigrateJwtToken (
428+ credential : { id : string ; jwtToken : string | null } ,
429+ decryptPassword : string | null ,
430+ ) {
431+ // if token is null, returns null
432+ if ( credential . jwtToken === null ) {
433+ return null ;
434+ }
435+
436+ if ( isClearTextToken ( credential . jwtToken ) ) {
437+ // JWT token is not encrypted => we encrypt it
438+ try {
439+ await getPrismaClient ( ) . organizationCredentials . update ( {
440+ where : { id : credential . id } ,
441+ data : { jwtToken : await encryptData ( credential . jwtToken , decryptPassword ) } ,
442+ } ) ;
443+ } catch {
444+ // migration failure is non-fatal
445+ }
446+ return credential . jwtToken ;
447+ } else {
448+ return decryptData ( credential . jwtToken , decryptPassword ) ;
449+ }
450+ }
451+
381452/* Validate organization credentials */
382453export async function organizationCredentialsInvalid (
383- org ?: ( OrganizationCredentials & { organization : Organization } ) | null ,
454+ org : ( OrganizationCredentials & { organization : Organization } ) | null ,
455+ decryptPassword : string | null ,
384456) {
385457 if ( ! org ) return true ;
386458
387459 if ( org . password . length === 0 || org . email . length === 0 ) return true ;
388460
389- const token = await getAccessToken ( org . organization . serverUrl ) ;
461+ const token = await getAccessToken ( org . organization . serverUrl , decryptPassword ) ;
390462 if ( ! token ) return true ;
391463
392464 try {
0 commit comments