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: