From a971455216550bd2af03bf8dde55490e8e010e14 Mon Sep 17 00:00:00 2001 From: ian Date: Wed, 31 Aug 2022 02:15:44 +0800 Subject: [PATCH] Add origin to accountability (#15273) * Add origin to accountability * Remove origin column from seeds --- api/src/auth/drivers/ldap.ts | 1 + api/src/auth/drivers/local.ts | 1 + api/src/auth/drivers/oauth2.ts | 1 + api/src/auth/drivers/openid.ts | 1 + api/src/controllers/activity.ts | 1 + api/src/controllers/auth.ts | 4 ++ .../20220826A-add-origin-to-accountability.ts | 21 ++++++++ .../database/system-data/fields/activity.yaml | 6 +++ .../database/system-data/fields/sessions.yaml | 2 + api/src/flows.ts | 1 + api/src/middleware/authenticate.test.ts | 48 +++++++++++++++++-- api/src/middleware/authenticate.ts | 1 + api/src/services/authentication.ts | 2 + api/src/services/graphql/index.ts | 6 +++ api/src/services/items.ts | 3 ++ api/src/services/shares.ts | 1 + app/src/composables/use-revisions.ts | 2 + app/src/lang/translations/en-US.yaml | 2 + app/src/modules/activity/routes/item.vue | 5 ++ app/src/types/revisions.ts | 1 + packages/shared/src/types/accountability.ts | 1 + packages/specs/src/components/activity.yaml | 4 ++ 22 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 api/src/database/migrations/20220826A-add-origin-to-accountability.ts diff --git a/api/src/auth/drivers/ldap.ts b/api/src/auth/drivers/ldap.ts index 6b110081dc..6d528bc7a6 100644 --- a/api/src/auth/drivers/ldap.ts +++ b/api/src/auth/drivers/ldap.ts @@ -373,6 +373,7 @@ export function createLDAPAuthRouter(provider: string): Router { const accountability = { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }; diff --git a/api/src/auth/drivers/local.ts b/api/src/auth/drivers/local.ts index d6c76214e7..1384292b57 100644 --- a/api/src/auth/drivers/local.ts +++ b/api/src/auth/drivers/local.ts @@ -62,6 +62,7 @@ export function createLocalAuthRouter(provider: string): Router { const accountability = { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }; diff --git a/api/src/auth/drivers/oauth2.ts b/api/src/auth/drivers/oauth2.ts index a2ecfe8871..10d1698710 100644 --- a/api/src/auth/drivers/oauth2.ts +++ b/api/src/auth/drivers/oauth2.ts @@ -276,6 +276,7 @@ export function createOAuth2AuthRouter(providerName: string): Router { accountability: { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }, schema: req.schema, diff --git a/api/src/auth/drivers/openid.ts b/api/src/auth/drivers/openid.ts index 7b4d2c09c9..c256303043 100644 --- a/api/src/auth/drivers/openid.ts +++ b/api/src/auth/drivers/openid.ts @@ -300,6 +300,7 @@ export function createOpenIDAuthRouter(providerName: string): Router { accountability: { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }, schema: req.schema, diff --git a/api/src/controllers/activity.ts b/api/src/controllers/activity.ts index c4a3fde6f0..5ef3fa0988 100644 --- a/api/src/controllers/activity.ts +++ b/api/src/controllers/activity.ts @@ -92,6 +92,7 @@ router.post( user: req.accountability?.user, ip: getIPFromReq(req), user_agent: req.get('user-agent'), + origin: req.get('origin'), }); try { diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index 864fe28650..42771142b2 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -59,6 +59,7 @@ router.post( const accountability = { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }; @@ -101,6 +102,7 @@ router.post( const accountability = { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }; @@ -141,6 +143,7 @@ router.post( const accountability = { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }; @@ -175,6 +178,7 @@ router.post( const accountability = { ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), role: null, }; diff --git a/api/src/database/migrations/20220826A-add-origin-to-accountability.ts b/api/src/database/migrations/20220826A-add-origin-to-accountability.ts new file mode 100644 index 0000000000..d51ab29be2 --- /dev/null +++ b/api/src/database/migrations/20220826A-add-origin-to-accountability.ts @@ -0,0 +1,21 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable('directus_activity', (table) => { + table.string('origin').nullable(); + }); + + await knex.schema.alterTable('directus_sessions', (table) => { + table.string('origin').nullable(); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable('directus_activity', (table) => { + table.dropColumn('origin'); + }); + + await knex.schema.alterTable('directus_sessions', (table) => { + table.dropColumn('origin'); + }); +} diff --git a/api/src/database/system-data/fields/activity.yaml b/api/src/database/system-data/fields/activity.yaml index d440af8450..e6a5f11eb0 100644 --- a/api/src/database/system-data/fields/activity.yaml +++ b/api/src/database/system-data/fields/activity.yaml @@ -60,6 +60,12 @@ fields: font: monospace width: half + - field: origin + display: formatted-value + display_options: + font: monospace + width: half + - field: ip display: formatted-value display_options: diff --git a/api/src/database/system-data/fields/sessions.yaml b/api/src/database/system-data/fields/sessions.yaml index 7c2af8e849..a97323c880 100644 --- a/api/src/database/system-data/fields/sessions.yaml +++ b/api/src/database/system-data/fields/sessions.yaml @@ -11,4 +11,6 @@ fields: width: half - field: user_agent width: half + - field: origin + width: half - field: share diff --git a/api/src/flows.ts b/api/src/flows.ts index 5d6160e351..0e43516960 100644 --- a/api/src/flows.ts +++ b/api/src/flows.ts @@ -331,6 +331,7 @@ class FlowManager { collection: 'directus_flows', ip: accountability?.ip ?? null, user_agent: accountability?.userAgent ?? null, + origin: accountability?.origin ?? null, item: flow.id, }); diff --git a/api/src/middleware/authenticate.test.ts b/api/src/middleware/authenticate.test.ts index 055239ab97..f11d9fc6ae 100644 --- a/api/src/middleware/authenticate.test.ts +++ b/api/src/middleware/authenticate.test.ts @@ -39,7 +39,16 @@ test('Short-circuits when authenticate filter is used', async () => { test('Uses default public accountability when no token is given', async () => { const req = { ip: '127.0.0.1', - get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)), + get: jest.fn((string) => { + switch (string) { + case 'user-agent': + return 'fake-user-agent'; + case 'origin': + return 'fake-origin'; + default: + return null; + } + }), }; const res = {}; @@ -56,6 +65,7 @@ test('Uses default public accountability when no token is given', async () => { app: false, ip: '127.0.0.1', userAgent: 'fake-user-agent', + origin: 'fake-origin', }); expect(next).toHaveBeenCalledTimes(1); @@ -87,7 +97,16 @@ test('Sets accountability to payload contents if valid token is passed', async ( const req = { ip: '127.0.0.1', - get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)), + get: jest.fn((string) => { + switch (string) { + case 'user-agent': + return 'fake-user-agent'; + case 'origin': + return 'fake-origin'; + default: + return null; + } + }), token, }; @@ -105,6 +124,7 @@ test('Sets accountability to payload contents if valid token is passed', async ( share_scope: shareScope, ip: '127.0.0.1', userAgent: 'fake-user-agent', + origin: 'fake-origin', }); expect(next).toHaveBeenCalledTimes(1); @@ -136,6 +156,7 @@ test('Sets accountability to payload contents if valid token is passed', async ( share_scope: shareScope, ip: '127.0.0.1', userAgent: 'fake-user-agent', + origin: 'fake-origin', }); expect(next).toHaveBeenCalledTimes(1); @@ -152,7 +173,16 @@ test('Throws InvalidCredentialsException when static token is used, but user doe const req = { ip: '127.0.0.1', - get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)), + get: jest.fn((string) => { + switch (string) { + case 'user-agent': + return 'fake-user-agent'; + case 'origin': + return 'fake-origin'; + default: + return null; + } + }), token: 'static-token', }; @@ -166,7 +196,16 @@ test('Throws InvalidCredentialsException when static token is used, but user doe test('Sets accountability to user information when static token is used', async () => { const req = { ip: '127.0.0.1', - get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)), + get: jest.fn((string) => { + switch (string) { + case 'user-agent': + return 'fake-user-agent'; + case 'origin': + return 'fake-origin'; + default: + return null; + } + }), token: 'static-token', }; @@ -182,6 +221,7 @@ test('Sets accountability to user information when static token is used', async admin: testUser.admin_access, ip: '127.0.0.1', userAgent: 'fake-user-agent', + origin: 'fake-origin', }; jest.mocked(getDatabase).mockReturnValue({ diff --git a/api/src/middleware/authenticate.ts b/api/src/middleware/authenticate.ts index 1427ac9a55..8083a976f8 100644 --- a/api/src/middleware/authenticate.ts +++ b/api/src/middleware/authenticate.ts @@ -21,6 +21,7 @@ export const handler = async (req: Request, res: Response, next: NextFunction) = app: false, ip: getIPFromReq(req), userAgent: req.get('user-agent'), + origin: req.get('origin'), }; const database = getDatabase(); diff --git a/api/src/services/authentication.ts b/api/src/services/authentication.ts index 3dcb3f02e7..b0cf0060b2 100644 --- a/api/src/services/authentication.ts +++ b/api/src/services/authentication.ts @@ -216,6 +216,7 @@ export class AuthenticationService { expires: refreshTokenExpiration, ip: this.accountability?.ip, user_agent: this.accountability?.userAgent, + origin: this.accountability?.origin, }); await this.knex('directus_sessions').delete().where('expires', '<', new Date()); @@ -226,6 +227,7 @@ export class AuthenticationService { user: user.id, ip: this.accountability.ip, user_agent: this.accountability.userAgent, + origin: this.accountability.origin, collection: 'directus_users', item: user.id, }); diff --git a/api/src/services/graphql/index.ts b/api/src/services/graphql/index.ts index 62df83ce5a..c0b2dd2e5f 100644 --- a/api/src/services/graphql/index.ts +++ b/api/src/services/graphql/index.ts @@ -1955,6 +1955,7 @@ export class GraphQLService { const accountability = { ip: req?.ip, userAgent: req?.get('user-agent'), + origin: req?.get('origin'), role: null, }; const authenticationService = new AuthenticationService({ @@ -1988,6 +1989,7 @@ export class GraphQLService { const accountability = { ip: req?.ip, userAgent: req?.get('user-agent'), + origin: req?.get('origin'), role: null, }; const authenticationService = new AuthenticationService({ @@ -2024,6 +2026,7 @@ export class GraphQLService { const accountability = { ip: req?.ip, userAgent: req?.get('user-agent'), + origin: req?.get('origin'), role: null, }; const authenticationService = new AuthenticationService({ @@ -2048,6 +2051,7 @@ export class GraphQLService { const accountability = { ip: req?.ip, userAgent: req?.get('user-agent'), + origin: req?.get('origin'), role: null, }; const service = new UsersService({ accountability, schema: this.schema }); @@ -2073,6 +2077,7 @@ export class GraphQLService { const accountability = { ip: req?.ip, userAgent: req?.get('user-agent'), + origin: req?.get('origin'), role: null, }; const service = new UsersService({ accountability, schema: this.schema }); @@ -2680,6 +2685,7 @@ export class GraphQLService { user: this.accountability?.user, ip: this.accountability?.ip, user_agent: this.accountability?.userAgent, + origin: this.accountability?.origin, }); if ('directus_activity' in ReadCollectionTypes) { diff --git a/api/src/services/items.ts b/api/src/services/items.ts index eefaaf43d2..356b737667 100644 --- a/api/src/services/items.ts +++ b/api/src/services/items.ts @@ -185,6 +185,7 @@ export class ItemsService implements AbstractSer collection: this.collection, ip: this.accountability!.ip, user_agent: this.accountability!.userAgent, + origin: this.accountability!.origin, item: primaryKey, }); @@ -549,6 +550,7 @@ export class ItemsService implements AbstractSer collection: this.collection, ip: this.accountability!.ip, user_agent: this.accountability!.userAgent, + origin: this.accountability!.origin, item: key, })) ); @@ -763,6 +765,7 @@ export class ItemsService implements AbstractSer collection: this.collection, ip: this.accountability!.ip, user_agent: this.accountability!.userAgent, + origin: this.accountability!.origin, item: key, })) ); diff --git a/api/src/services/shares.ts b/api/src/services/shares.ts index 6da28a1a73..d678496879 100644 --- a/api/src/services/shares.ts +++ b/api/src/services/shares.ts @@ -101,6 +101,7 @@ export class SharesService extends ItemsService { expires: refreshTokenExpiration, ip: this.accountability?.ip, user_agent: this.accountability?.userAgent, + origin: this.accountability?.origin, share: record.share_id, }); diff --git a/app/src/composables/use-revisions.ts b/app/src/composables/use-revisions.ts index 91b8a0da1d..0e7390d858 100644 --- a/app/src/composables/use-revisions.ts +++ b/app/src/composables/use-revisions.ts @@ -79,6 +79,7 @@ export function useRevisions(collection: Ref, primaryKey: Ref, primaryKey: Ref{{ t('user_agent') }}:

{{ item.user_agent }}

+

{{ t('origin') }}:

+

{{ item.origin }}

+

{{ t('collection') }}:

{{ item.collection }}

@@ -62,6 +65,7 @@ type ActivityRecord = { timestamp: string; ip: string; user_agent: string; + origin: string; collection: string; item: string; }; @@ -109,6 +113,7 @@ export default defineComponent({ 'timestamp', 'ip', 'user_agent', + 'origin', 'collection', 'item', ], diff --git a/app/src/types/revisions.ts b/app/src/types/revisions.ts index f6a36f7945..7f380d72da 100644 --- a/app/src/types/revisions.ts +++ b/app/src/types/revisions.ts @@ -8,6 +8,7 @@ export type Revision = { action: string; ip: string; user_agent: string; + origin: string; timestamp: string; user: | string diff --git a/packages/shared/src/types/accountability.ts b/packages/shared/src/types/accountability.ts index 23c3034804..71b417beab 100644 --- a/packages/shared/src/types/accountability.ts +++ b/packages/shared/src/types/accountability.ts @@ -16,4 +16,5 @@ export type Accountability = { ip?: string; userAgent?: string; + origin?: string; }; diff --git a/packages/specs/src/components/activity.yaml b/packages/specs/src/components/activity.yaml index bd75ebe821..0ed43af8f4 100644 --- a/packages/specs/src/components/activity.yaml +++ b/packages/specs/src/components/activity.yaml @@ -37,6 +37,10 @@ properties: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/78.0.3904.108 Safari/537.36 type: string + origin: + description: Origin of the request when the action took place. + example: https://directus.io + type: string collection: description: Collection identifier in which the item resides. oneOf: