Skip to content

Commit

Permalink
feat(auth): tenanted grants
Browse files Browse the repository at this point in the history
  • Loading branch information
njlie committed Dec 17, 2024
1 parent a8b7ca4 commit 8eb71fc
Show file tree
Hide file tree
Showing 20 changed files with 599 additions and 217 deletions.
20 changes: 20 additions & 0 deletions packages/auth/migrations/20241206232423_add_tenant_to_grant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema.alterTable('grants', function (table) {
table.uuid('tenantId').notNullable()
table.foreign('tenantId').references('tenants.id')
})
}

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.alterTable('grants', function (table) {
table.dropColumn('tenantId')
})
}
24 changes: 8 additions & 16 deletions packages/auth/src/access/service.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { faker } from '@faker-js/faker'
import nock from 'nock'
import { Knex } from 'knex'
import { v4 } from 'uuid'
import { createTestApp, TestContainer } from '../tests/app'
import { truncateTables } from '../tests/tableManager'
import { Config } from '../config/app'
import { IocContract } from '@adonisjs/fold'
import { initIocContainer } from '../'
import { AppServices } from '../app'
import { AccessService } from './service'
import { Grant, GrantState, StartMethod, FinishMethod } from '../grant/model'
import { Grant } from '../grant/model'
import { IncomingPaymentRequest, OutgoingPaymentRequest } from './types'
import { generateNonce, generateToken } from '../shared/utils'
import { generateBaseGrant } from '../tests/grant'
import { AccessType, AccessAction } from '@interledger/open-payments'
import { Access } from './model'
import { Tenant } from '../tenant/model'
import { generateTenant } from '../tests/tenant'

describe('Access Service', (): void => {
let deps: IocContract<AppServices>
Expand All @@ -22,19 +22,11 @@ describe('Access Service', (): void => {
let trx: Knex.Transaction
let grant: Grant

const generateBaseGrant = () => ({
state: GrantState.Pending,
startMethod: [StartMethod.Redirect],
continueToken: generateToken(),
continueId: v4(),
finishMethod: FinishMethod.Redirect,
finishUri: 'https://example.com/finish',
clientNonce: generateNonce(),
client: faker.internet.url({ appendSlash: false })
})

beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch(generateBaseGrant())
const tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch(
generateBaseGrant({ tenantId: tenant.id })
)
})

beforeAll(async (): Promise<void> => {
Expand Down
10 changes: 8 additions & 2 deletions packages/auth/src/access/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { createTestApp, TestContainer } from '../tests/app'
import { truncateTables } from '../tests/tableManager'
import { generateToken, generateNonce } from '../shared/utils'
import { compareRequestAndGrantAccessItems } from './utils'
import { Tenant } from '../tenant/model'
import { generateTenant } from '../tests/tenant'

describe('Access utilities', (): void => {
let deps: IocContract<AppServices>
Expand All @@ -25,6 +27,7 @@ describe('Access utilities', (): void => {
let identifier: string
let grant: Grant
let grantAccessItem: Access
let tenant: Tenant

const receiver: string =
'https://wallet.com/alice/incoming-payments/12341234-1234-1234-1234-123412341234'
Expand All @@ -36,6 +39,7 @@ describe('Access utilities', (): void => {

beforeEach(async (): Promise<void> => {
identifier = `https://example.com/${v4()}`
tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch({
state: GrantState.Processing,
startMethod: [StartMethod.Redirect],
Expand All @@ -44,7 +48,8 @@ describe('Access utilities', (): void => {
finishMethod: FinishMethod.Redirect,
finishUri: 'https://example.com/finish',
clientNonce: generateNonce(),
client: faker.internet.url({ appendSlash: false })
client: faker.internet.url({ appendSlash: false }),
tenantId: tenant.id
})

grantAccessItem = await Access.query(trx).insertAndFetch({
Expand Down Expand Up @@ -241,7 +246,8 @@ describe('Access utilities', (): void => {
finishMethod: FinishMethod.Redirect,
finishUri: 'https://example.com/finish',
clientNonce: generateNonce(),
client: faker.internet.url({ appendSlash: false })
client: faker.internet.url({ appendSlash: false }),
tenantId: tenant.id
})

const grantAccessItem = await Access.query(trx).insertAndFetch({
Expand Down
20 changes: 17 additions & 3 deletions packages/auth/src/accessToken/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
import { GrantService } from '../grant/service'
import { AccessTokenService } from './service'
import { GNAPErrorCode } from '../shared/gnapErrors'
import { generateTenant } from '../tests/tenant'
import { Tenant } from '../tenant/model'

describe('Access Token Routes', (): void => {
let deps: IocContract<AppServices>
Expand Down Expand Up @@ -96,7 +98,11 @@ describe('Access Token Routes', (): void => {
const method = 'POST'

beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch(BASE_GRANT)
const tenant = await Tenant.query().insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
tenantId: tenant.id
})
access = await Access.query(trx).insertAndFetch({
grantId: grant.id,
...BASE_ACCESS
Expand Down Expand Up @@ -367,7 +373,11 @@ describe('Access Token Routes', (): void => {
let token: AccessToken

beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch(BASE_GRANT)
const tenant = await Tenant.query().insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
tenantId: tenant.id
})
token = await AccessToken.query(trx).insertAndFetch({
grantId: grant.id,
...BASE_TOKEN
Expand Down Expand Up @@ -406,7 +416,11 @@ describe('Access Token Routes', (): void => {
let token: AccessToken

beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch(BASE_GRANT)
const tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
tenantId: tenant.id
})
access = await Access.query(trx).insertAndFetch({
grantId: grant.id,
...BASE_ACCESS
Expand Down
13 changes: 11 additions & 2 deletions packages/auth/src/accessToken/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
AccessItem
} from '@interledger/open-payments'
import { generateBaseGrant } from '../tests/grant'
import { Tenant } from '../tenant/model'
import { generateTenant } from '../tests/tenant'

describe('Access Token Service', (): void => {
let deps: IocContract<AppServices>
Expand Down Expand Up @@ -63,8 +65,9 @@ describe('Access Token Service', (): void => {

let grant: Grant
beforeEach(async (): Promise<void> => {
const tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch(
generateBaseGrant({ state: GrantState.Approved })
generateBaseGrant({ state: GrantState.Approved, tenantId: tenant.id })
)
grant.access = [
await Access.query(trx).insertAndFetch({
Expand Down Expand Up @@ -186,8 +189,9 @@ describe('Access Token Service', (): void => {
})

test('Introspection only returns requested access', async (): Promise<void> => {
const tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
const grantWithTwoAccesses = await Grant.query(trx).insertAndFetch(
generateBaseGrant({ state: GrantState.Approved })
generateBaseGrant({ state: GrantState.Approved, tenantId: tenant.id })
)
grantWithTwoAccesses.access = [
await Access.query(trx).insertAndFetch({
Expand Down Expand Up @@ -247,11 +251,14 @@ describe('Access Token Service', (): void => {
})

describe('Revoke', (): void => {
let tenant: Tenant
let grant: Grant
let token: AccessToken
beforeEach(async (): Promise<void> => {
tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch(
generateBaseGrant({
tenantId: tenant.id,
state: GrantState.Finalized,
finalizationReason: GrantFinalization.Issued
})
Expand Down Expand Up @@ -352,8 +359,10 @@ describe('Access Token Service', (): void => {
let token: AccessToken
let originalTokenValue: string
beforeEach(async (): Promise<void> => {
const tenant = await Tenant.query(trx).insertAndFetch(generateTenant())
grant = await Grant.query(trx).insertAndFetch(
generateBaseGrant({
tenantId: tenant.id,
state: GrantState.Finalized,
finalizationReason: GrantFinalization.Issued
})
Expand Down
6 changes: 3 additions & 3 deletions packages/auth/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export class App {
/* Back-channel GNAP Routes */
// Grant Initiation
router.post<DefaultState, CreateContext>(
'/',
'/:tenantId/',
createValidatorMiddleware<CreateContext>(openApi.authServerSpec, {
path: '/',
method: HttpMethod.POST
Expand All @@ -278,7 +278,7 @@ export class App {

// Grant Continue
router.post<DefaultState, ContinueContext>(
'/continue/:id',
'/:tenantId/continue/:id',
createValidatorMiddleware<ContinueContext>(openApi.authServerSpec, {
path: '/continue/{id}',
method: HttpMethod.POST
Expand All @@ -289,7 +289,7 @@ export class App {

// Grant Cancel
router.delete<DefaultState, GrantRevokeContext>(
'/continue/:id',
'/:tenantId/continue/:id',
createValidatorMiddleware<GrantRevokeContext>(openApi.authServerSpec, {
path: '/continue/{id}',
method: HttpMethod.DELETE
Expand Down
21 changes: 21 additions & 0 deletions packages/auth/src/grant/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@interledger/open-payments'
import { AccessToken, toOpenPaymentsAccessToken } from '../accessToken/model'
import { Interaction } from '../interaction/model'
import { Tenant } from '../tenant/model'

export enum StartMethod {
Redirect = 'redirect'
Expand Down Expand Up @@ -61,6 +62,14 @@ export class Grant extends BaseModel {
from: 'grants.id',
to: 'interactions.grantId'
}
},
tenant: {
relation: Model.HasOneRelation,
modelClass: join(__dirname, '../tenant/model'),
join: {
from: 'grants.tenantId',
to: 'tenants.id'
}
}
})
public access!: Access[]
Expand All @@ -79,6 +88,10 @@ export class Grant extends BaseModel {

public lastContinuedAt!: Date

public tenantId!: string

public tenant?: Tenant

public $beforeInsert(context: QueryContext): void {
super.$beforeInsert(context)
this.lastContinuedAt = new Date()
Expand Down Expand Up @@ -192,3 +205,11 @@ export function isRevokedGrant(grant: Grant): boolean {
grant.finalizationReason === GrantFinalization.Revoked
)
}

export interface GrantWithTenant extends Grant {
tenant: NonNullable<Grant['tenant']>
}

export function isGrantWithTenant(grant: Grant): grant is GrantWithTenant {
return !!grant.tenant
}
Loading

0 comments on commit 8eb71fc

Please sign in to comment.