Skip to content

Commit

Permalink
Changing Signature to JWT in Armory app
Browse files Browse the repository at this point in the history
  • Loading branch information
mattschoch committed Mar 12, 2024
1 parent 0dcb973 commit 5edab35
Show file tree
Hide file tree
Showing 25 changed files with 107 additions and 175 deletions.
24 changes: 4 additions & 20 deletions apps/armory/src/__test__/fixture/authorization-request.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { Decision, Signature, TransactionRequest } from '@narval/policy-engine-shared'
import { Decision, TransactionRequest } from '@narval/policy-engine-shared'
import { AuthorizationRequestStatus } from '@prisma/client/armory'
import { z } from 'zod'
import { Fixture } from 'zod-fixture'
import { Approval, AuthorizationRequest, SignTransaction } from '../../orchestration/core/type/domain.type'
import { AuthorizationRequest, SignTransaction } from '../../orchestration/core/type/domain.type'
import { readRequestSchema } from '../../orchestration/persistence/schema/request.schema'
import { readSignTransactionSchema } from '../../orchestration/persistence/schema/sign-transaction.schema'
import { signatureSchema } from '../../orchestration/persistence/schema/signature.schema'
import { readTransactionRequestSchema } from '../../orchestration/persistence/schema/transaction-request.schema'
import { addressGenerator, chainIdGenerator, hexGenerator } from './shared.fixture'

const approvalSchema = signatureSchema.extend({
id: z.string().uuid(),
createdAt: z.date()
})

const evaluationSchema = z.object({
id: z.string().uuid(),
decision: z.nativeEnum(Decision),
Expand All @@ -26,8 +20,8 @@ const authorizationRequestSchema = z.object({
orgId: z.string().uuid(),
status: z.nativeEnum(AuthorizationRequestStatus),
request: readRequestSchema,
authentication: signatureSchema,
approvals: z.array(approvalSchema),
authentication: z.string(),
approvals: z.array(z.string()),
evaluations: z.array(evaluationSchema),
idempotencyKey: z.string().nullish(),
createdAt: z.date(),
Expand Down Expand Up @@ -66,13 +60,3 @@ export const generateAuthorizationRequest = (partial?: Partial<AuthorizationRequ
...partial
}
}

export const generateApproval = (partial?: Partial<Approval>): Approval => ({
...new Fixture().fromSchema(approvalSchema),
...partial
})

export const generateSignature = (partial?: Partial<Signature>): Signature => ({
...new Fixture().fromSchema(approvalSchema),
...partial
})
3 changes: 1 addition & 2 deletions apps/armory/src/__test__/fixture/feed.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { times } from 'lodash/fp'
import { z } from 'zod'
import { Fixture } from 'zod-fixture'
import { HistoricalTransferFeedService } from '../../data-feed/core/service/historical-transfer-feed.service'
import { signatureSchema } from '../../orchestration/persistence/schema/signature.schema'
import { hexGenerator } from './shared.fixture'
import { generateTransfer } from './transfer-tracking.fixture'

const feedSchema = z.object({
source: z.string().min(1).max(42),
sig: signatureSchema.nullable()
sig: z.string().nullable()
})

export const generateHistoricalTransfers = (): HistoricalTransfer[] =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { MockProxy, mock, mockDeep } from 'jest-mock-extended'
import {
generateAuthorizationRequest,
generateSignTransactionRequest,
generateSignature,
generateTransactionRequest
} from '../../../../../__test__/fixture/authorization-request.fixture'
import { generateHistoricalTransfers } from '../../../../../__test__/fixture/feed.fixture'
Expand All @@ -17,6 +16,9 @@ import { HistoricalTransferFeedService } from '../../historical-transfer-feed.se
import { PriceFeedService } from '../../price-feed.service'

describe(FeedService.name, () => {
const jwt =
'eyJraWQiOiIweDJjNDg5NTIxNTk3M0NiQmQ3NzhDMzJjNDU2QzA3NGI5OWRhRjhCZjEiLCJhbGciOiJFSVAxOTEiLCJ0eXAiOiJKV1QifQ.eyJyZXF1ZXN0SGFzaCI6IjYwOGFiZTkwOGNmZmVhYjFmYzMzZWRkZTZiNDQ1ODZmOWRhY2JjOWM2ZmU2ZjBhMTNmYTMwNzIzNzI5MGNlNWEiLCJzdWIiOiJ0ZXN0LXJvb3QtdXNlci11aWQiLCJpc3MiOiJodHRwczovL2FybW9yeS5uYXJ2YWwueHl6IiwiY25mIjp7Imt0eSI6IkVDIiwiY3J2Ijoic2VjcDI1NmsxIiwiYWxnIjoiRVMyNTZLIiwidXNlIjoic2lnIiwia2lkIjoiMHgwMDBjMGQxOTEzMDhBMzM2MzU2QkVlMzgxM0NDMTdGNjg2ODk3MkM0IiwieCI6IjA0YTlmM2JjZjY1MDUwNTk1OTdmNmYyN2FkOGMwZjAzYTNiZDdhMTc2MzUyMGIwYmZlYzIwNDQ4OGI4ZTU4NDAiLCJ5IjoiN2VlOTI4NDVhYjFjMzVhNzg0YjA1ZmRmYTU2NzcxNWM1M2JiMmYyOTk0OWIyNzcxNGUzYzE3NjBlMzcwOTAwOWE2In19.gFDywYsxY2-uT6H6hyxk51CtJhAZpI8WtcvoXHltiWsoBVOot1zMo3nHAhkWlYRmD3RuLtmOYzi6TwTUM8mFyBs'

let module: TestingModule
let service: FeedService
let prismaServiceMock: MockProxy<PrismaService>
Expand All @@ -33,13 +35,13 @@ describe(FeedService.name, () => {

const historicalTransferFeed: Feed<HistoricalTransfer[]> = {
source: HistoricalTransferFeedService.SOURCE_ID,
sig: generateSignature(),
sig: jwt,
data: generateHistoricalTransfers()
}

const priceFeed: Feed<Prices> = {
source: PriceFeedService.SOURCE_ID,
sig: generateSignature(),
sig: jwt,
data: generatePrices()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Alg } from '@narval/signature'
import { ConfigModule } from '@nestjs/config'
import { Test, TestingModule } from '@nestjs/testing'
import { MockProxy, mock } from 'jest-mock-extended'
Expand Down Expand Up @@ -70,12 +69,7 @@ describe(HistoricalTransferFeedService.name, () => {
expect(feed).toMatchObject({
data: HistoricalTransferFeedService.build(transfers),
source: HistoricalTransferFeedService.SOURCE_ID,
sig: {
alg: Alg.ES256K,
pubKey:
'0x041a2a9746efacc23443530a75092ad75c6cd5dd10d2ccc1d9c866acf9545974bcacc6b755c3c241d6a35b9b27b00cd2df8f46525a751d6872360c3be3015bb563',
sig: expect.any(String)
}
sig: expect.any(String)
})
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Prices } from '@narval/policy-engine-shared'
import { Alg } from '@narval/signature'
import { ConfigModule } from '@nestjs/config'
import { Test, TestingModule } from '@nestjs/testing'
import { MockProxy, mock } from 'jest-mock-extended'
Expand Down Expand Up @@ -73,12 +72,7 @@ describe(PriceFeedService.name, () => {
expect(feed).toMatchObject({
data: prices,
source: PriceFeedService.SOURCE_ID,
sig: {
alg: Alg.ES256K,
pubKey:
'0x04583c9cf37f209ca3afbdb43f83dfaed0e758b89545bfa8e297d3d727fc3ed9c6f16607bc859f2304704329bf19c843acce511d0639bbd8abb1fe70e7dd05f8f5',
sig: expect.any(String)
}
sig: expect.any(String)
})
})

Expand Down
4 changes: 1 addition & 3 deletions apps/armory/src/data-feed/core/service/feed.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ export class FeedService {
orgId,
requestId,
source: feed.source,
sig: feed.sig?.sig,
alg: feed.sig?.alg,
pubKey: feed.sig?.pubKey,
sig: feed.sig,
data: this.getPersistableJson(feed.data),
createdAt: new Date()
}))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Feed, HistoricalTransfer, Signature } from '@narval/policy-engine-shared'
import { Alg, hash } from '@narval/signature'
import { Feed, HistoricalTransfer, JwtString } from '@narval/policy-engine-shared'
import { Payload, SigningAlg, hash, hexToBase64Url, privateKeyToJwk, signJwt } from '@narval/signature'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { mapValues, omit } from 'lodash/fp'
Expand Down Expand Up @@ -27,17 +27,26 @@ export class HistoricalTransferFeedService implements DataFeed<HistoricalTransfe
return privateKeyToAccount(this.getPrivateKey()).publicKey
}

async sign(data: HistoricalTransfer[]): Promise<Signature> {
async sign(data: HistoricalTransfer[]): Promise<JwtString> {
const account = privateKeyToAccount(this.getPrivateKey())
const sig = await account.signMessage({
message: hash(data)
})

return {
alg: Alg.ES256K,
pubKey: account.publicKey,
sig
const jwtSigner = async (msg: string) => {
const jwtSig = await account.signMessage({ message: msg })

return hexToBase64Url(jwtSig)
}

const now = Math.floor(Date.now() / 1000)
const jwk = privateKeyToJwk(this.getPrivateKey())
const payload: Payload = {
data: hash(data),
sub: account.address,
iss: 'https://armory.narval.xyz',
iat: now
}
const jwt = await signJwt(payload, jwk, { alg: SigningAlg.EIP191 }, jwtSigner)

return jwt
}

private getPrivateKey(): `0x${string}` {
Expand Down
29 changes: 19 additions & 10 deletions apps/armory/src/data-feed/core/service/price-feed.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Action, AssetId, Feed, Signature } from '@narval/policy-engine-shared'
import { Alg, hash } from '@narval/signature'
import { Action, AssetId, Feed, JwtString } from '@narval/policy-engine-shared'
import { Payload, SigningAlg, hash, hexToBase64Url, privateKeyToJwk, signJwt } from '@narval/signature'
import { InputType, Intents, safeDecode } from '@narval/transaction-request-intent'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
Expand All @@ -26,17 +26,26 @@ export class PriceFeedService implements DataFeed<Prices> {
return PriceFeedService.SOURCE_ID
}

async sign(data: Prices): Promise<Signature> {
async sign(data: Prices): Promise<JwtString> {
const account = privateKeyToAccount(this.getPrivateKey())
const sig = await account.signMessage({
message: hash(data)
})

return {
alg: Alg.ES256K,
pubKey: account.publicKey,
sig
const jwtSigner = async (msg: string) => {
const jwtSig = await account.signMessage({ message: msg })

return hexToBase64Url(jwtSig)
}

const now = Math.floor(Date.now() / 1000)
const jwk = privateKeyToJwk(this.getPrivateKey())
const payload: Payload = {
data: hash(data),
sub: account.address,
iss: 'https://armory.narval.xyz',
iat: now
}
const jwt = await signJwt(payload, jwk, { alg: SigningAlg.EIP191 }, jwtSigner)

return jwt
}

getPubKey(): string {
Expand Down
4 changes: 2 additions & 2 deletions apps/armory/src/data-feed/core/type/data-feed.type.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Feed, Signature } from '@narval/policy-engine-shared'
import { Feed } from '@narval/policy-engine-shared'
// TODO (@wcalderipe, 06/02/24): Move the AuthorizationRequest type to shared
import { AuthorizationRequest } from '../../../orchestration/core/type/domain.type'

export interface DataFeed<Data> {
getId(): string
getPubKey(): string
getFeed(input: AuthorizationRequest): Promise<Feed<Data>>
sign(data: Data): Promise<Signature>
sign(data: Data): Promise<string>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { Test, TestingModule } from '@nestjs/testing'
import { MockProxy, mock } from 'jest-mock-extended'
import { times } from 'lodash/fp'
import {
generateApproval,
generateAuthorizationRequest,
generateSignTransactionRequest,
generateSignature,
generateTransactionRequest
} from '../../../../../__test__/fixture/authorization-request.fixture'
import { generateTransfer } from '../../../../../__test__/fixture/transfer-tracking.fixture'
Expand All @@ -20,16 +18,14 @@ import { TransferTrackingService } from '../../../../../transfer-tracking/core/s
import { AuthorizationRequestAlreadyProcessingException } from '../../../../core/exception/authorization-request-already-processing.exception'
import { AuthorizationRequestService } from '../../../../core/service/authorization-request.service'
import { ClusterService } from '../../../../core/service/cluster.service'
import {
Approval,
AuthorizationRequest,
AuthorizationRequestStatus,
SignTransaction
} from '../../../../core/type/domain.type'
import { AuthorizationRequest, AuthorizationRequestStatus, SignTransaction } from '../../../../core/type/domain.type'
import { AuthorizationRequestRepository } from '../../../../persistence/repository/authorization-request.repository'
import { AuthorizationRequestProcessingProducer } from '../../../../queue/producer/authorization-request-processing.producer'

describe(AuthorizationRequestService.name, () => {
const jwt =
'eyJraWQiOiIweDJjNDg5NTIxNTk3M0NiQmQ3NzhDMzJjNDU2QzA3NGI5OWRhRjhCZjEiLCJhbGciOiJFSVAxOTEiLCJ0eXAiOiJKV1QifQ.eyJyZXF1ZXN0SGFzaCI6IjYwOGFiZTkwOGNmZmVhYjFmYzMzZWRkZTZiNDQ1ODZmOWRhY2JjOWM2ZmU2ZjBhMTNmYTMwNzIzNzI5MGNlNWEiLCJzdWIiOiJ0ZXN0LXJvb3QtdXNlci11aWQiLCJpc3MiOiJodHRwczovL2FybW9yeS5uYXJ2YWwueHl6IiwiY25mIjp7Imt0eSI6IkVDIiwiY3J2Ijoic2VjcDI1NmsxIiwiYWxnIjoiRVMyNTZLIiwidXNlIjoic2lnIiwia2lkIjoiMHgwMDBjMGQxOTEzMDhBMzM2MzU2QkVlMzgxM0NDMTdGNjg2ODk3MkM0IiwieCI6IjA0YTlmM2JjZjY1MDUwNTk1OTdmNmYyN2FkOGMwZjAzYTNiZDdhMTc2MzUyMGIwYmZlYzIwNDQ4OGI4ZTU4NDAiLCJ5IjoiN2VlOTI4NDVhYjFjMzVhNzg0YjA1ZmRmYTU2NzcxNWM1M2JiMmYyOTk0OWIyNzcxNGUzYzE3NjBlMzcwOTAwOWE2In19.gFDywYsxY2-uT6H6hyxk51CtJhAZpI8WtcvoXHltiWsoBVOot1zMo3nHAhkWlYRmD3RuLtmOYzi6TwTUM8mFyBs'

let module: TestingModule
let authzRequestRepositoryMock: MockProxy<AuthorizationRequestRepository>
let authzRequestProcessingProducerMock: MockProxy<AuthorizationRequestProcessingProducer>
Expand Down Expand Up @@ -90,11 +86,9 @@ describe(AuthorizationRequestService.name, () => {
})

describe('approve', () => {
const approval: Approval = generateApproval()

const updatedAuthzRequest: AuthorizationRequest = {
...authzRequest,
approvals: [approval]
approvals: [jwt]
}

beforeEach(() => {
Expand All @@ -106,11 +100,11 @@ describe(AuthorizationRequestService.name, () => {
it('creates a new approval and evaluates the authorization request', async () => {
authzRequestRepositoryMock.update.mockResolvedValue(updatedAuthzRequest)

await service.approve(authzRequest.id, approval)
await service.approve(authzRequest.id, jwt)

expect(authzRequestRepositoryMock.update).toHaveBeenCalledWith({
id: authzRequest.id,
approvals: [approval]
approvals: [jwt]
})
expect(service.evaluate).toHaveBeenCalledWith(updatedAuthzRequest)
})
Expand All @@ -120,7 +114,9 @@ describe(AuthorizationRequestService.name, () => {
const evaluationResponse: EvaluationResponse = {
decision: Decision.PERMIT,
request: authzRequest.request,
attestation: generateSignature(),
accessToken: {
value: jwt
},
transactionRequestIntent: {
type: Intents.TRANSFER_NATIVE,
amount: '1000000000000000000',
Expand Down Expand Up @@ -176,7 +172,7 @@ describe(AuthorizationRequestService.name, () => {
expect.objectContaining({
id: expect.any(String),
decision: evaluationResponse.decision,
signature: evaluationResponse.attestation?.sig,
signature: evaluationResponse.accessToken?.value,
createdAt: expect.any(Date)
})
]
Expand Down Expand Up @@ -207,7 +203,7 @@ describe(AuthorizationRequestService.name, () => {
chainId: request.transactionRequest.chainId,
orgId: authzRequest.orgId,
requestId: authzRequest.id,
initiatedBy: authzRequest.authentication.pubKey,
initiatedBy: authzRequest.authentication, // TODO: this will change when the underlying data is corrected.
rates: {
'fiat:usd': 0.99
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Decision, EvaluationResponse, Feed, Prices } from '@narval/policy-engine-shared'
import { Alg, hash } from '@narval/signature'
import { hash } from '@narval/signature'
import { Test } from '@nestjs/testing'
import { MockProxy, mock } from 'jest-mock-extended'
import { PrivateKeyAccount, generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
generateAuthorizationRequest,
generateSignTransactionRequest,
generateSignature,
generateTransactionRequest
} from '../../../../../__test__/fixture/authorization-request.fixture'
import { generatePrices } from '../../../../../__test__/fixture/price.fixture'
Expand All @@ -21,6 +20,9 @@ import { AuthorizationRequest } from '../../../../core/type/domain.type'
import { AuthzApplicationClient } from '../../../../http/client/authz-application.client'

describe(ClusterService.name, () => {
const jwt =
'eyJraWQiOiIweDJjNDg5NTIxNTk3M0NiQmQ3NzhDMzJjNDU2QzA3NGI5OWRhRjhCZjEiLCJhbGciOiJFSVAxOTEiLCJ0eXAiOiJKV1QifQ.eyJyZXF1ZXN0SGFzaCI6IjYwOGFiZTkwOGNmZmVhYjFmYzMzZWRkZTZiNDQ1ODZmOWRhY2JjOWM2ZmU2ZjBhMTNmYTMwNzIzNzI5MGNlNWEiLCJzdWIiOiJ0ZXN0LXJvb3QtdXNlci11aWQiLCJpc3MiOiJodHRwczovL2FybW9yeS5uYXJ2YWwueHl6IiwiY25mIjp7Imt0eSI6IkVDIiwiY3J2Ijoic2VjcDI1NmsxIiwiYWxnIjoiRVMyNTZLIiwidXNlIjoic2lnIiwia2lkIjoiMHgwMDBjMGQxOTEzMDhBMzM2MzU2QkVlMzgxM0NDMTdGNjg2ODk3MkM0IiwieCI6IjA0YTlmM2JjZjY1MDUwNTk1OTdmNmYyN2FkOGMwZjAzYTNiZDdhMTc2MzUyMGIwYmZlYzIwNDQ4OGI4ZTU4NDAiLCJ5IjoiN2VlOTI4NDVhYjFjMzVhNzg0YjA1ZmRmYTU2NzcxNWM1M2JiMmYyOTk0OWIyNzcxNGUzYzE3NjBlMzcwOTAwOWE2In19.gFDywYsxY2-uT6H6hyxk51CtJhAZpI8WtcvoXHltiWsoBVOot1zMo3nHAhkWlYRmD3RuLtmOYzi6TwTUM8mFyBs'

let service: ClusterService
let authzApplicationClientMock: MockProxy<AuthzApplicationClient>

Expand Down Expand Up @@ -51,7 +53,7 @@ describe(ClusterService.name, () => {

const priceFeed: Feed<Prices> = {
source: PriceFeedService.SOURCE_ID,
sig: generateSignature(),
sig: jwt,
data: generatePrices()
}

Expand Down Expand Up @@ -110,10 +112,8 @@ describe(ClusterService.name, () => {
satisfied: [],
missing: []
},
attestation: {
sig: signature,
alg: Alg.ES256K,
pubKey: account.address
accessToken: {
value: signature
},
...partial
}
Expand Down Expand Up @@ -173,11 +173,7 @@ describe(ClusterService.name, () => {

authzApplicationClientMock.evaluation.mockResolvedValue({
...permit,
attestation: {
alg: Alg.ES256K,
sig: signature,
pubKey: nodeAccount.address
}
accessToken: { value: signature }
})

await expect(service.evaluation(input)).rejects.toThrow(InvalidAttestationSignatureException)
Expand Down
Loading

0 comments on commit 5edab35

Please sign in to comment.