Skip to content

Commit

Permalink
chore(backend): store and check grant expiresAt
Browse files Browse the repository at this point in the history
  • Loading branch information
wilsonianb committed Nov 28, 2022
1 parent 4a453a9 commit aa571e1
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 32 deletions.
9 changes: 9 additions & 0 deletions packages/backend/src/open_payments/grant/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export class Grant extends BaseModel {
return 'grants'
}

static get virtualAttributes(): string[] {
return ['expired']
}

static relationMappings = {
authServer: {
relation: Model.BelongsToOneRelation,
Expand All @@ -26,6 +30,11 @@ export class Grant extends BaseModel {
public accessToken?: string
public accessType!: AccessType
public accessActions!: AccessAction[]
public expiresAt?: Date

public get expired(): boolean {
return !!this.expiresAt && this.expiresAt <= new Date()
}

$afterFind(queryContext: QueryContext): void {
super.$afterFind(queryContext)
Expand Down
84 changes: 55 additions & 29 deletions packages/backend/src/open_payments/grant/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IocContract } from '@adonisjs/fold'
import { faker } from '@faker-js/faker'
import { Knex } from 'knex'

import { Grant } from './model'
import { GrantOptions, GrantService } from './service'
import { AccessType, AccessAction } from '../auth/grant'
import { AuthServer } from '../authServer/model'
Expand Down Expand Up @@ -36,49 +37,74 @@ describe('Grant Service', (): void => {
})

describe('Create and Get Grant', (): void => {
test.each`
newAuthServer
${false}
${true}
`(
'Grant can be created and fetched (new auth server: $newAuthServer)',
async ({ newAuthServer }): Promise<void> => {
const authServerService = await deps.use('authServerService')
let authServerId: string | undefined
const authServerUrl = faker.internet.url()
if (newAuthServer) {
describe.each`
existingAuthServer | description
${false} | ${'new auth server'}
${true} | ${'existing auth server'}
`('$description', ({ existingAuthServer }): void => {
let authServerId: string | undefined
let grant: Grant | undefined
const authServerUrl = faker.internet.url()

beforeEach(async (): Promise<void> => {
if (existingAuthServer) {
const authServerService = await deps.use('authServerService')
authServerId = (await authServerService.getOrCreate(authServerUrl)).id
} else {
await expect(
AuthServer.query(knex).findOne({
url: authServerUrl
})
).resolves.toBeUndefined()
} else {
authServerId = (await authServerService.getOrCreate(authServerUrl)).id
authServerId = undefined
}
const options: GrantOptions = {
authServer: authServerUrl,
accessType: AccessType.IncomingPayment,
accessActions: [AccessAction.ReadAll]
}
const grant = await grantService.create(options)
expect(grant).toMatchObject({
accessType: options.accessType,
accessActions: options.accessActions
})
if (newAuthServer) {
jest.useFakeTimers()
jest.setSystemTime(Date.now())
})

afterEach(async (): Promise<void> => {
jest.useRealTimers()
if (existingAuthServer) {
expect(grant.authServerId).toEqual(authServerId)
} else {
await expect(
AuthServer.query(knex).findOne({
url: authServerUrl
})
).resolves.toMatchObject({
id: grant.authServerId
})
} else {
expect(grant.authServerId).toEqual(authServerId)
}
await expect(grantService.get(options)).resolves.toEqual(grant)
}
)
})

test.each`
expiresIn | description
${undefined} | ${'without expiresIn'}
${600} | ${'with expiresIn'}
`(
'Grant can be created and fetched ($description)',
async ({ expiresIn }): Promise<void> => {
const options: GrantOptions = {
authServer: authServerUrl,
accessType: AccessType.IncomingPayment,
accessActions: [AccessAction.ReadAll]
}
grant = await grantService.create({
...options,
expiresIn
})
expect(grant).toMatchObject({
accessType: options.accessType,
accessActions: options.accessActions,
expiresAt: expiresIn
? new Date(Date.now() + expiresIn * 1000)
: null
})
expect(grant.expired).toBe(false)
await expect(grantService.get(options)).resolves.toEqual(grant)
}
)
})

test('cannot fetch non-existing grant', async (): Promise<void> => {
const options: GrantOptions = {
Expand Down
6 changes: 5 additions & 1 deletion packages/backend/src/open_payments/grant/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface GrantOptions {

export interface CreateOptions extends GrantOptions {
accessToken?: string
expiresIn?: number
}

async function createGrant(deps: ServiceDependencies, options: CreateOptions) {
Expand All @@ -46,7 +47,10 @@ async function createGrant(deps: ServiceDependencies, options: CreateOptions) {
accessType: options.accessType,
accessActions: options.accessActions,
accessToken: options.accessToken,
authServerId
authServerId,
expiresAt: options.expiresIn
? new Date(Date.now() + options.expiresIn * 1000)
: undefined
})
}

Expand Down
26 changes: 25 additions & 1 deletion packages/backend/src/open_payments/receiver/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,31 @@ describe('Receiver Service', (): void => {
})
})

if (!existingGrant) {
if (existingGrant) {
test('returns undefined for expired grant', async (): Promise<void> => {
const grant = await grantService.get({
...grantOptions,
authServer
})
await grant.$query(knex).patch({ expiresAt: new Date() })
jest
.spyOn(openPaymentsClient.paymentPointer, 'get')
.mockResolvedValueOnce(
paymentPointer.toOpenPaymentsType({
authServer
})
)
const clientRequestGrantSpy = jest.spyOn(
openPaymentsClient.grant,
'request'
)

await expect(
receiverService.get(incomingPayment.url)
).resolves.toBeUndefined()
expect(clientRequestGrantSpy).not.toHaveBeenCalled()
})
} else {
test('returns undefined for invalid grant', async (): Promise<void> => {
jest
.spyOn(openPaymentsClient.paymentPointer, 'get')
Expand Down
10 changes: 9 additions & 1 deletion packages/backend/src/open_payments/receiver/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ async function getIncomingPaymentGrant(

const existingGrant = await deps.grantService.get(grantOptions)
if (existingGrant) {
if (existingGrant.expired) {
// TODO
// https://github.com/interledger/rafiki/issues/795
deps.logger.warn({ grantOptions }, 'Grant access token expired')
return undefined
}
return existingGrant
}

Expand All @@ -229,8 +235,10 @@ async function getIncomingPaymentGrant(
if (isNonInteractiveGrant(grant)) {
return await deps.grantService.create({
...grantOptions,
accessToken: grant.access_token.value
accessToken: grant.access_token.value,
expiresIn: grant.access_token.expires_in
})
}
deps.logger.warn({ grantOptions }, 'Grant request required interaction')
return undefined
}

0 comments on commit aa571e1

Please sign in to comment.