Skip to content

Commit 4faf201

Browse files
authored
CP-13547: Added logging to seedlesswallet to track down false == true error (#3639)
1 parent f8952d5 commit 4faf201

File tree

7 files changed

+235
-78
lines changed

7 files changed

+235
-78
lines changed

packages/core-mobile/app/new/common/utils/startRefreshSeedlessTokenFlow.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,23 @@ export async function startRefreshSeedlessTokenFlow(
2121
> {
2222
const oidcProvider = await SecureStorageService.load(
2323
KeySlot.OidcProvider
24-
).catch(Logger.error)
24+
).catch(e => {
25+
Logger.error(
26+
'[RefreshSeedlessTokenFlow] failed to load OidcProvider from keychain',
27+
e
28+
)
29+
return undefined
30+
})
2531
const oidcUserId = await SecureStorageService.load(KeySlot.OidcUserId).catch(
26-
Logger.error
32+
e => {
33+
Logger.error(
34+
'[RefreshSeedlessTokenFlow] failed to load OidcUserId from keychain',
35+
e
36+
)
37+
return undefined
38+
}
2739
)
40+
2841
let oidcTokenResult: OidcPayload
2942

3043
try {
@@ -45,6 +58,10 @@ export async function startRefreshSeedlessTokenFlow(
4558
}
4659
}
4760
} catch (e) {
61+
Logger.error(
62+
`[RefreshSeedlessTokenFlow] OIDC sign-in failed for provider: ${oidcProvider}`,
63+
e
64+
)
4865
return {
4966
success: false,
5067
error: new RefreshSeedlessTokenFlowErrors({

packages/core-mobile/app/security/SecureStorageService.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Aes from 'react-native-aes-crypto'
44
import { decrypt, encrypt } from 'utils/EncryptionHelper'
55
import { serializeJson } from 'utils/serialization/serialize'
66
import { deserializeJson } from 'utils/serialization/deserialize'
7+
import Logger from 'utils/Logger'
78

89
export enum KeySlot {
910
SignerSessionData = 'SignerSessionData',
@@ -41,7 +42,15 @@ class SecureStorageService {
4142
service: serviceForValues
4243
}
4344
)
44-
assert(result !== false)
45+
if (result === false) {
46+
Logger.error(
47+
`[SecureStorage] store(${slot}) - setGenericPassword returned false! Keychain write FAILED`
48+
)
49+
}
50+
assert(
51+
result !== false,
52+
`[SecureStorage] store(${slot}) - Keychain write failed for service: ${serviceForValues}`
53+
)
4554
}
4655

4756
/**
@@ -55,7 +64,15 @@ class SecureStorageService {
5564
const result = await Keychain.getGenericPassword({
5665
service: serviceForValues
5766
})
58-
assert(result !== false)
67+
if (result === false) {
68+
Logger.error(
69+
`[SecureStorage] load(${slot}) - getGenericPassword returned false! No data in keychain for service: ${serviceForValues}`
70+
)
71+
}
72+
assert(
73+
result !== false,
74+
`[SecureStorage] load(${slot}) - Keychain read failed for service: ${serviceForValues}`
75+
)
5976
const decrypted = await decrypt(result.password, key)
6077
const stringified = decrypted.data
6178
return deserializeJson<T>(stringified)
@@ -86,11 +103,33 @@ class SecureStorageService {
86103
if (existingCredentials) {
87104
return existingCredentials.password
88105
}
106+
107+
// Check whether encrypted data already exists for this slot.
108+
// If it does, the key was lost and data is unrecoverable — report to Sentry.
109+
// If not, this is a normal first-run initialization.
110+
const serviceForValues = `ss_value_${slot}`
111+
const existingData = await Keychain.getGenericPassword({
112+
service: serviceForValues
113+
})
114+
115+
if (existingData) {
116+
Logger.error(
117+
`[SecureStorage] getOrCreateKey(${slot}) - encryption key MISSING but encrypted data EXISTS. Data for this slot is UNRECOVERABLE.`
118+
)
119+
} else {
120+
Logger.warn(
121+
`[SecureStorage] getOrCreateKey(${slot}) - no existing encryption key found, generating new key (expected on first run).`
122+
)
123+
}
124+
89125
const key: string = await Aes.randomKey(32)
90126
const result = await Keychain.setGenericPassword(serviceForKeys, key, {
91127
service: serviceForKeys
92128
})
93-
assert(result !== false)
129+
assert(
130+
result !== false,
131+
`[SecureStorage] getOrCreateKey(${slot}) - Keychain write failed for service: ${serviceForKeys}`
132+
)
94133
return key
95134
}
96135
}

packages/core-mobile/app/seedless/services/SeedlessSession.ts

Lines changed: 126 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ class SeedlessSession {
8989
* Retrieves information about the current user's mfa.
9090
*/
9191
async userMfa(): Promise<MFA[]> {
92-
return (await this.aboutMe()).mfa
92+
try {
93+
return (await this.aboutMe()).mfa
94+
} catch (error) {
95+
Logger.error(`[SeedlessSession] userMfa() FAILED`, error)
96+
throw error
97+
}
9398
}
9499

95100
/**
@@ -99,42 +104,50 @@ class SeedlessSession {
99104
oidcToken: string,
100105
mfaReceipt?: MfaReceipt | undefined
101106
): Promise<CubeSignerResponse<SessionData>> {
102-
const response = await CubeSignerClient.createOidcSession(
103-
envInterface,
104-
SEEDLESS_ORG_ID,
105-
oidcToken,
106-
this.scopes,
107-
{
108-
// How long singing with a particular token works from the token creation
109-
auth_lifetime: minutesToSeconds(5),
110-
// How long a refresh token is valid, the user has to unlock Core in this timeframe otherwise they will have to re-login
111-
// Sessions expire either if the session lifetime expires or if a refresh token expires before a new one is generated
112-
refresh_lifetime: hoursToSeconds(90 * 24),
113-
// How long till the user absolutely must sign in again
114-
session_lifetime: hoursToSeconds(365 * 24)
115-
},
116-
mfaReceipt
117-
)
107+
try {
108+
const response = await CubeSignerClient.createOidcSession(
109+
envInterface,
110+
SEEDLESS_ORG_ID,
111+
oidcToken,
112+
this.scopes,
113+
{
114+
// How long singing with a particular token works from the token creation
115+
auth_lifetime: minutesToSeconds(5),
116+
// How long a refresh token is valid, the user has to unlock Core in this timeframe otherwise they will have to re-login
117+
// Sessions expire either if the session lifetime expires or if a refresh token expires before a new one is generated
118+
refresh_lifetime: hoursToSeconds(90 * 24),
119+
// How long till the user absolutely must sign in again
120+
session_lifetime: hoursToSeconds(365 * 24)
121+
},
122+
mfaReceipt
123+
)
124+
125+
if (response.requiresMfa()) {
126+
// if MFA is required, we cannot access session data until MFA verification is complete
127+
// thus, we are just storing the MFA client here (for verification operations)
128+
const client = await response.mfaClient()
129+
if (!client) throw new Error('MFA client is missing')
130+
this.signerClient = client
131+
} else {
132+
// when MFA is not required or MFA verification is complete
133+
// we can safely access session data
134+
const sessionData = response.data()
135+
136+
// destroy outdated client instance
137+
this.signerClient = null
138+
139+
// persist session data
140+
await this.sessionManager.store(sessionData)
141+
}
118142

119-
if (response.requiresMfa()) {
120-
// if MFA is required, we cannot access session data until MFA verification is complete
121-
// thus, we are just storing the MFA client here (for verification operations)
122-
const client = await response.mfaClient()
123-
if (!client) throw new Error('MFA client is missing')
124-
this.signerClient = client
125-
} else {
126-
// when MFA is not required or MFA verification is complete
127-
// we can safely access session data
128-
const sessionData = response.data()
129-
130-
// destroy outdated client instance
131-
this.signerClient = null
132-
133-
// persist session data
134-
await this.sessionManager.store(sessionData)
143+
return response
144+
} catch (error) {
145+
Logger.error(
146+
`[SeedlessSession] requestOidcAuth() FAILED - hasMfaReceipt: ${!!mfaReceipt}`,
147+
error
148+
)
149+
throw error
135150
}
136-
137-
return response
138151
}
139152

140153
async refreshToken(): Promise<Result<void, Error>> {
@@ -152,7 +165,15 @@ class SeedlessSession {
152165
err.status === 403 &&
153166
err.isSessionExpiredError()
154167
) {
168+
Logger.warn(
169+
`[SeedlessSession] refreshToken() - 403 session expired. errorCode: ${err.errorCode}, calling onSessionExpired`
170+
)
155171
this.onSessionExpired?.()
172+
} else {
173+
Logger.error(
174+
`[SeedlessSession] refreshToken() - forceRefresh failed with non-403 error`,
175+
err
176+
)
156177
}
157178

158179
return {
@@ -163,45 +184,70 @@ class SeedlessSession {
163184
}
164185

165186
async totpResetInit(): Promise<CubeSignerResponse<TotpChallenge>> {
166-
const cubeSignerClient = await this.getSignerClient()
167-
168-
return await cubeSignerClient.resetTotp('Core')
187+
try {
188+
const cubeSignerClient = await this.getSignerClient()
189+
return await cubeSignerClient.resetTotp('Core')
190+
} catch (error) {
191+
Logger.error(`[SeedlessSession] totpResetInit() FAILED`, error)
192+
throw error
193+
}
169194
}
170195

171196
async fidoRegisterInit(
172197
name: string
173198
): Promise<CubeSignerResponse<AddFidoChallenge>> {
174-
const cubeSignerClient = await this.getSignerClient()
175-
return await cubeSignerClient.addFido(name)
199+
try {
200+
const cubeSignerClient = await this.getSignerClient()
201+
return await cubeSignerClient.addFido(name)
202+
} catch (error) {
203+
Logger.error(`[SeedlessSession] fidoRegisterInit() FAILED`, error)
204+
throw error
205+
}
176206
}
177207

178208
async deleteFido(fidoId: string): Promise<CubeSignerResponse<Empty>> {
179-
const cubeSignerClient = await this.getSignerClient()
180-
return await cubeSignerClient.deleteFido(fidoId)
209+
try {
210+
const cubeSignerClient = await this.getSignerClient()
211+
return await cubeSignerClient.deleteFido(fidoId)
212+
} catch (error) {
213+
Logger.error(
214+
`[SeedlessSession] deleteFido() FAILED - fidoId: ${fidoId}`,
215+
error
216+
)
217+
throw error
218+
}
181219
}
182220

183221
async approveFido(
184222
oidcToken: string,
185223
mfaId: string,
186224
withSecurityKey: boolean
187225
): Promise<void> {
188-
const challenge = await this.fidoApproveStart(mfaId)
189-
const credential = await PasskeyService.getCredential(
190-
challenge.options,
191-
withSecurityKey
192-
)
193-
194-
const mfaRequestInfo = await challenge.answer(credential)
195-
const mfaReceipt = await mfaRequestInfo.receipt()
196-
197-
if (mfaReceipt?.mfaConf) {
198-
await this.requestOidcAuth(oidcToken, {
199-
mfaOrgId: SEEDLESS_ORG_ID,
200-
mfaId: mfaId,
201-
mfaConf: mfaReceipt.mfaConf
202-
})
203-
} else {
204-
throw new Error('Passkey authentication failed')
226+
try {
227+
const challenge = await this.fidoApproveStart(mfaId)
228+
const credential = await PasskeyService.getCredential(
229+
challenge.options,
230+
withSecurityKey
231+
)
232+
233+
const mfaRequestInfo = await challenge.answer(credential)
234+
const mfaReceipt = await mfaRequestInfo.receipt()
235+
236+
if (mfaReceipt?.mfaConf) {
237+
await this.requestOidcAuth(oidcToken, {
238+
mfaOrgId: SEEDLESS_ORG_ID,
239+
mfaId: mfaId,
240+
mfaConf: mfaReceipt.mfaConf
241+
})
242+
} else {
243+
throw new Error('Passkey authentication failed')
244+
}
245+
} catch (error) {
246+
Logger.error(
247+
`[SeedlessSession] approveFido() FAILED - mfaId: ${mfaId}`,
248+
error
249+
)
250+
throw error
205251
}
206252
}
207253

@@ -241,7 +287,11 @@ class SeedlessSession {
241287
})
242288

243289
return { success: true, value: undefined }
244-
} catch {
290+
} catch (error) {
291+
Logger.error(
292+
`[SeedlessSession] verifyCode() FAILED - mfaId: ${mfaId}`,
293+
error
294+
)
245295
return {
246296
success: false,
247297
error: new TotpErrors({
@@ -257,8 +307,16 @@ class SeedlessSession {
257307
* MfaFidoChallenge.answer or fidoApproveComplete.
258308
*/
259309
async fidoApproveStart(mfaId: string): Promise<MfaFidoChallenge> {
260-
const signerClient = await this.getSignerClient()
261-
return signerClient.apiClient.mfaFidoInit(mfaId)
310+
try {
311+
const signerClient = await this.getSignerClient()
312+
return signerClient.apiClient.mfaFidoInit(mfaId)
313+
} catch (error) {
314+
Logger.error(
315+
`[SeedlessSession] fidoApproveStart() FAILED - mfaId: ${mfaId}`,
316+
error
317+
)
318+
throw error
319+
}
262320
}
263321

264322
/**
@@ -292,6 +350,12 @@ class SeedlessSession {
292350

293351
this.signerClient = client
294352
return client
353+
} catch (error) {
354+
Logger.error(
355+
`[SeedlessSession] getSignerClient() - CubeSignerClient.create() FAILED`,
356+
error
357+
)
358+
throw error
295359
} finally {
296360
// Always reset the promise lock (success or failure) so later calls can retry.
297361
this.signerClientPromise = null

0 commit comments

Comments
 (0)