Skip to content

Commit

Permalink
Merge pull request #94 from jolocom/fix/token-storage-validation
Browse files Browse the repository at this point in the history
Refactor Interactions to enable loading historical Interactions
  • Loading branch information
chunningham authored Jan 13, 2021
2 parents 8e9f056 + 54f9624 commit 1c973fa
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 54 deletions.
14 changes: 12 additions & 2 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,21 @@ export class Agent {
if (err.message !== ErrorCode.NoSuchInteraction) throw err
}

// extract ProofOfControlAuthority (PCA) and process it if available
if (token.payload.pca) {
// update local state
await this.sdk.didMethods
.getForDid(token.issuer)
.registrar.encounter(token.payload.pca)
}

if (!interxn) {
// NOTE: interactionManager.start calls processInteractionToken internally
// NOTE: interactionManager.start internally calls
// processInteractionToken and storage.store
interxn = await this.interactionManager.start(token, transportAPI)
} else if (interxn.lastMessage.encode() !== jwt) {
// NOTE FIXME TODO #multitenancy
// we only process the message if it is not last message seen
// we only process the message if it is not last message seen (see "else if" condition)
// this is to allow for some flexibility with how processJWT is called,
// mostly because of how there is no separation between interaction tokens
// stored by different agents sharing the same database.
Expand All @@ -348,6 +357,7 @@ export class Agent {
// flexibility and instead always run processInteractionToken on incoming
// JWTs
await interxn.processInteractionToken(token)
await this.storage.store.interactionToken(token)
}

return interxn
Expand Down
90 changes: 38 additions & 52 deletions src/interactionManager/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ export class Interaction<F extends Flow<any> = Flow<any>> extends Transportable

/**
* Returns an Interaction with state calculated from the given list of
* messages. The messages are *not* committed to storage in the process,
* because this method is intended to be used to reload previously stored
* interactions. See {@link InteractionManager.getInteraction}
* messages. The messages are *not* committed to storage or validated in the
* process, because this method is intended to be used to reload previously
* stored interactions. See {@link InteractionManager.getInteraction}
*
* @param messages - List of messages to calculate interaction state from
* @param ctx - The manager of this interaction
Expand All @@ -156,9 +156,9 @@ export class Interaction<F extends Flow<any> = Flow<any>> extends Transportable
transportAPI
)

// we process all the tokens sequentially
// we process all the tokens sequentially, withot revalidating
for (let message of messages) {
await interaction.processInteractionToken(message)
await interaction._processToken(message)
}
return interaction
}
Expand Down Expand Up @@ -399,29 +399,9 @@ export class Interaction<F extends Flow<any> = Flow<any>> extends Transportable
)
}

/**
* Validate an interaction token and process it to update the interaction
* state (via the associated {@link Flow})
*
* @param token - the token to
* @returns Promise<boolean> whether or not processing was successful
* @throws SDKError<InvalidToken> with `origError` set to the original token
* validation error from the jolocom library
* @category Basic
*
*/
public async processInteractionToken<T>(
private async _processToken<T>(
token: JSONWebToken<T>,
): Promise<boolean> {

// extract PCA
if (token.payload.pca) {
// update local state
await this.ctx.ctx.sdk.didMethods
.getForDid(token.issuer)
.registrar.encounter(token.payload.pca)
}

if (!this.participants.requester) {
// TODO what happens if the signer isnt resolvable
try {
Expand All @@ -445,32 +425,6 @@ export class Interaction<F extends Flow<any> = Flow<any>> extends Transportable
}
}

// verify
try {
await this.ctx.ctx.idw.validateJWT(
token,
this.messages[this.messages.length - 1],
this.ctx.ctx.resolver
)
} catch (err) {
throw new SDKError(ErrorCode.InvalidToken, err)
}

// TODO if handling fails, should we still be pushing the token??
const res = await this.flow.handleInteractionToken(token.interactionToken, token.interactionType)
this.messages.push(token)

const storedTokens = (await this.ctx.ctx.storage.get.interactionTokens({
nonce: token.nonce,
type: token.interactionType,
issuer: token.issuer
})).map(t => t.encode())
const tokenStr = token.encode()
// check if token is already in storage before attempting to commit it to
// storage
if (!storedTokens.some(t => t === tokenStr))
await this.ctx.ctx.storage.store.interactionToken(token)

if (!this._transportAPI) {
// update transportAPI
// @ts-ignore
Expand All @@ -491,9 +445,41 @@ export class Interaction<F extends Flow<any> = Flow<any>> extends Transportable
}
}

// TODO if handling fails, should we still be pushing the token??
const res = await this.flow.handleInteractionToken(token.interactionToken, token.interactionType)
this.messages.push(token)

return res
}

/**
* Validate an interaction token and process it to update the interaction
* state (via the associated {@link Flow})
*
* @param token - the token to validate and process
* @returns Promise<boolean> whether or not processing was successful
* @throws SDKError<InvalidToken> with `origError` set to the original token
* validation error from the jolocom library
* @category Basic
*
*/
public async processInteractionToken<T>(
token: JSONWebToken<T>,
): Promise<boolean> {
// verify
try {
await this.ctx.ctx.idw.validateJWT(
token,
this.messages.length ? this.messages[this.messages.length - 1] : undefined,
this.ctx.ctx.resolver
)
} catch (err) {
throw new SDKError(ErrorCode.InvalidToken, err)
}

return this._processToken(token)
}

/**
* @category Asymm Crypto
*/
Expand Down
1 change: 1 addition & 0 deletions src/interactionManager/interactionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class InteractionManager {
)
this.interactions[token.nonce] = interaction
await interaction.processInteractionToken(token)
await this.ctx.storage.store.interactionToken(token)

return interaction
}
Expand Down
11 changes: 11 additions & 0 deletions tests/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ describe('findInteraction', () => {
})

describe('reconstructing an interaction from stored messages', () => {
it('returns an interaction even if already expired', async () => {
jest.useFakeTimers('modern')
jest.setSystemTime(0)
const jwt = await alice.authRequestToken({ callbackURL: 'dummy', description: 'test' })
jest.useRealTimers()
const alice2 = await alice.sdk.initAgent({})
const interxn = await alice2.findInteraction(jwt.nonce)

expect(() => interxn.transportAPI).not.toThrow()
})

it('returns an interaction that has a transportAPI', async () => {
const jwt = await alice.authRequestToken({ callbackURL: 'dummy', description: 'test' })

Expand Down
1 change: 1 addition & 0 deletions tests/authRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ test('Authentication interaction', async () => {
})

const bobInteraction = await bob.processJWT(aliceAuthRequest.encode())
expect(() => bobInteraction.transportAPI).not.toThrow()

const bobResponse = (
await bobInteraction.createAuthenticationResponse()
Expand Down
1 change: 1 addition & 0 deletions tests/credIssuance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ test('Credential Issuance interaction', async () => {
})

const bobInteraction = await bob.processJWT(aliceCredOffer.encode())
expect(() => bobInteraction.transportAPI).not.toThrow()

const bobResponse = (
await bobInteraction.createCredentialOfferResponseToken([
Expand Down
1 change: 1 addition & 0 deletions tests/credRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ test('Credential Request interaction', async () => {
})

const bobInteraction = await bob.processJWT(aliceCredReq.encode())
expect(() => bobInteraction.transportAPI).not.toThrow()

const bobResponse = (
await bobInteraction.createCredentialResponse([bobSelfSignedCred.id])
Expand Down

0 comments on commit 1c973fa

Please sign in to comment.