Skip to content

Commit

Permalink
feat: Check that the sourceAccountIdentifier corresponds to account name
Browse files Browse the repository at this point in the history
Clisk konnectors will now be allowed to have multiple accounts for the
same konnector.

To make it possible, the flagship app will allways check that the
sourceAccountIdentifier returned by the konnector, corresponds to the
accountName saved in the account. If there is a difference, it means
that the worker webview is authenticated with the wrong account and it
will ask to the konnector to authenticate again.

If the a wrong sourceAccountIdentifier is return twice, an error is
raised.
  • Loading branch information
doubleface authored and doubleface committed Oct 24, 2024
1 parent f3c71ff commit f573d15
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 22 deletions.
61 changes: 55 additions & 6 deletions src/libs/ReactNativeLauncher.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,15 +318,21 @@ class ReactNativeLauncher extends Launcher {
await this.pilot.call('ensureNotAuthenticated')
}
}
await this.pilot.call('ensureAuthenticated', { account: prevAccount })

const userDataResult = await this.pilot.call('getUserDataFromWebsite')
if (!userDataResult?.sourceAccountIdentifier) {
throw new Error(
'getUserDataFromWebsite did not return any sourceAccountIdentifier. Cannot continue the execution.'
try {
this.setUserData(
await this.initAndCheckSourceAccountIdentifier(prevAccount)
)
} catch (err) {
if (err.message === 'WRONG_ACCOUNT_IDENTIFIER') {
await this.pilot.call('ensureNotAuthenticated')
this.setUserData(
await this.initAndCheckSourceAccountIdentifier(prevAccount)
)
} else {
throw err
}
}
this.setUserData(userDataResult)

const ensureResult = await this.ensureAccountTriggerAndLaunch()
await this.createTimeoutTrigger()
Expand Down Expand Up @@ -369,6 +375,7 @@ class ReactNativeLauncher extends Launcher {
await this.stop()
} catch (err) {
log.error(JSON.stringify(err), 'start error')
log.error(err.message, 'start error message')
const automaticDebugTraceFlag = flag('clisk.automatic.html-on-error')
if (automaticDebugTraceFlag) {
this.emit('SHOW_TRACE_DEBUG_VIEW', err)
Expand All @@ -380,6 +387,48 @@ class ReactNativeLauncher extends Launcher {
this.emit('KONNECTOR_EXECUTION_END')
}

/**
* Try to run authentication phase of the konnector and check if the sourceAccountIdentifier
* is the same as the account, if any.
* @param {import('cozy-client/types/types').IOCozyAccount} account
* @returns {Object}
* @throws 'WRONG_ACCOUNT_IDENTIFIER'
*/
async initAndCheckSourceAccountIdentifier(account) {
this.log({
namespace: 'ReactNativeLauncher',
label: 'initAndCheckSourceAccountIdentifier',
level: 'debug',
msg: 'start'
})
await this.pilot.call('ensureAuthenticated', { account })

const userDataResult = await this.pilot.call('getUserDataFromWebsite')

if (!userDataResult?.sourceAccountIdentifier) {
throw new Error(
'getUserDataFromWebsite did not return any sourceAccountIdentifier. Cannot continue the execution.'
)
}
if (
account &&
userDataResult.sourceAccountIdentifier !== account?.auth?.accountName
) {
this.log({
namespace: 'ReactNativeLauncher',
label: '_start',
level: 'error',
msg:
'Wrong account identifier: ' +
userDataResult.sourceAccountIdentifier +
' should be ' +
account?.auth?.accountName
})
throw new Error('WRONG_ACCOUNT_IDENTIFIER')
}
return userDataResult
}

async fetchAndSaveDebugData(force) {
const flagvalue = flag('clisk.html-on-error')
if (!flagvalue && !force) {
Expand Down
62 changes: 46 additions & 16 deletions src/libs/ReactNativeLauncher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ import {

const fixtures = {
job: { _id: 'normal_job_id' },
account: { _id: 'normal_account_id' },
account: {
_id: 'normal_account_id',
auth: { accountName: 'testsourceaccountidentifier' }
},
trigger: {
_id: 'normal_trigger_id',
message: { folder_to_save: 'normalfolderid' }
message: { folder_to_save: 'testfolderid' }
}
}

Expand Down Expand Up @@ -148,10 +151,6 @@ describe('ReactNativeLauncher', () => {
state: null
})
expect(client.save).toHaveBeenNthCalledWith(2, {
_id: 'normal_account_id',
auth: { accountName: 'testsourceaccountidentifier' }
})
expect(client.save).toHaveBeenNthCalledWith(3, {
_type: 'io.cozy.triggers',
type: '@client',
worker: 'konnector',
Expand All @@ -162,7 +161,7 @@ describe('ReactNativeLauncher', () => {
}
})
expect(client.save).toHaveBeenNthCalledWith(
4,
3,
expect.objectContaining({
_type: 'io.cozy.triggers',
type: '@in',
Expand Down Expand Up @@ -251,14 +250,8 @@ describe('ReactNativeLauncher', () => {
}
})
)
expect(client.save).toHaveBeenNthCalledWith(1, {
_id: 'normal_account_id',
auth: {
accountName: 'testsourceaccountidentifier'
}
})
expect(client.save).toHaveBeenNthCalledWith(
2,
1,
expect.objectContaining({
_type: 'io.cozy.triggers',
type: '@in',
Expand All @@ -272,7 +265,7 @@ describe('ReactNativeLauncher', () => {
}
})
)
expect(client.save).toHaveBeenNthCalledWith(3, {
expect(client.save).toHaveBeenNthCalledWith(2, {
_id: 'normal_job_id',
attributes: {
state: 'done'
Expand Down Expand Up @@ -411,7 +404,7 @@ describe('ReactNativeLauncher', () => {
})
.mockRejectedValue(new Error('test error message'))
await launcher.start()
expect(client.save).toHaveBeenNthCalledWith(3, {
expect(client.save).toHaveBeenNthCalledWith(2, {
_id: 'normal_job_id',
attributes: {
state: 'errored',
Expand Down Expand Up @@ -564,6 +557,43 @@ describe('ReactNativeLauncher', () => {
{ account: fixtures.account }
)
})
it('should raise WRONG_ACCOUNT_IDENTIFIER when the user authenticates with the wrong identifiers twice', async () => {
const { launcher, client, launch } = setup()
const konnector = {
slug: 'konnectorslug',
clientSide: true,
permissions: { files: { type: 'io.cozy.files' } }
}
launcher.setStartContext({
client,
konnector,
account: fixtures.account,
trigger: fixtures.trigger,
manifest: konnector,
launcherClient: {
setAppMetadata: () => null
}
})
launch.mockResolvedValue({ data: fixtures.job })
client.query.mockResolvedValue({ data: fixtures.account, included: [] })
client.queryAll.mockResolvedValue([])
client.save.mockImplementation(async doc => ({
data: { ...doc, _id: doc._id ? doc._id : 'newid' }
}))
launcher.pilot.call.mockImplementation(method => {
if (method === 'getUserDataFromWebsite') {
return { sourceAccountIdentifier: 'wrongtestsourceaccountidentifier' }
} else {
return true
}
})
launcher.stop = jest.fn()
await launcher.start()
expect(launcher.pilot.call).toHaveBeenCalledWith('ensureNotAuthenticated')
expect(launcher.stop).toHaveBeenCalledWith({
message: 'WRONG_ACCOUNT_IDENTIFIER'
})
})
})
describe('runInWorker', () => {
it('should resolve with method result', async () => {
Expand Down

0 comments on commit f573d15

Please sign in to comment.