@cepseudo/adonis-audit-log 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -1,9 +1,9 @@
1
- # The MIT License
2
-
3
- Copyright (c) 2023
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
-
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
-
9
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ # The MIT License
2
+
3
+ Copyright (c) 2023
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,190 +1,201 @@
1
- # @cepseudo/adonis-audit-log
2
-
3
- Simple audit logging package for AdonisJS 6.
4
-
5
- ## Features
6
-
7
- - Custom audit logs for tracking user actions and business events
8
- - Automatic HTTP request logging via middleware
9
- - Error logging with exception handler integration
10
- - Configurable field sanitization for sensitive data
11
- - Retention settings for log cleanup
12
-
13
- ## Installation
14
-
15
- ```bash
16
- npm install @cepseudo/adonis-audit-log
17
- ```
18
-
19
- ## Configuration
20
-
21
- ```bash
22
- node ace configure @cepseudo/adonis-audit-log
23
- ```
24
-
25
- This will:
26
-
27
- 1. Publish migrations to `database/migrations/`
28
- 2. Publish config to `config/audit.ts`
29
- 3. Register the provider in `adonisrc.ts`
30
-
31
- Then run the migrations:
32
-
33
- ```bash
34
- node ace migration:run
35
- ```
36
-
37
- ## Configuration Options
38
-
39
- ```typescript
40
- // config/audit.ts
41
- import { defineConfig } from '@cepseudo/adonis-audit-log'
42
-
43
- export default defineConfig({
44
- enabled: true,
45
-
46
- requestLog: {
47
- enabled: true,
48
- excludeRoutes: ['/health', '/metrics'],
49
- excludeMethods: ['OPTIONS'],
50
- logBody: false,
51
- logQuery: true,
52
- sanitizeFields: ['password', 'token', 'secret'],
53
- },
54
-
55
- errorLog: {
56
- enabled: true,
57
- excludeStatusCodes: [404],
58
- includeStack: true,
59
- },
60
-
61
- auditLog: {
62
- enabled: true,
63
- },
64
-
65
- retention: {
66
- requestLogs: 30,
67
- errorLogs: 90,
68
- auditLogs: 365,
69
- },
70
- })
71
- ```
72
-
73
- ## Usage
74
-
75
- ### Custom Audit Logs
76
-
77
- ```typescript
78
- import audit from '@cepseudo/adonis-audit-log/services/main'
79
-
80
- // Simple log
81
- await audit.log('user.login', { user_id: 1 })
82
-
83
- // With metadata
84
- await audit.log('submission.create', {
85
- user_id: auth.user.id,
86
- submission_id: submission.id,
87
- project_acronym: submission.projectAcronym,
88
- })
89
-
90
- // Track changes
91
- await audit.logChange('user.update', {
92
- userId: auth.user.id,
93
- targetId: targetUser.id,
94
- changes: {
95
- email: { from: 'old@example.com', to: 'new@example.com' },
96
- },
97
- })
98
- ```
99
-
100
- ### Request Logging (Middleware)
101
-
102
- Add the middleware to your router in `start/kernel.ts`:
103
-
104
- ```typescript
105
- router.use([() => import('@cepseudo/adonis-audit-log/middleware/request_logger')])
106
- ```
107
-
108
- ### Error Logging (Exception Handler)
109
-
110
- Integrate with your exception handler in `app/exceptions/handler.ts`:
111
-
112
- ```typescript
113
- import { AuditErrorLogger } from '@cepseudo/adonis-audit-log'
114
-
115
- export default class HttpExceptionHandler extends ExceptionHandler {
116
- async report(error: unknown, ctx: HttpContext) {
117
- await AuditErrorLogger.log(error, ctx)
118
- return super.report(error, ctx)
119
- }
120
- }
121
- ```
122
-
123
- ## Database Schema
124
-
125
- The package creates three tables:
126
-
127
- ### audit_logs
128
-
129
- - `id` - Primary key
130
- - `action_type` - Event type (e.g., 'user.login', 'submission.create')
131
- - `metadata` - JSON object with additional data
132
- - `user_id` - Optional reference to users table
133
- - `created_at` - Timestamp
134
-
135
- ### request_logs
136
-
137
- - `id` - Primary key
138
- - `method` - HTTP method
139
- - `url` - Request URL
140
- - `route_name` - Named route if available
141
- - `status_code` - HTTP response status
142
- - `response_time_ms` - Response time in milliseconds
143
- - `ip` - Client IP address
144
- - `user_agent` - Browser/client user agent
145
- - `user_id` - Optional reference to users table
146
- - `request_body` - Sanitized request body (optional)
147
- - `request_query` - Query parameters
148
- - `created_at` - Timestamp
149
-
150
- ### error_logs
151
-
152
- - `id` - Primary key
153
- - `error_type` - Error class name
154
- - `message` - Error message
155
- - `stack` - Stack trace (optional)
156
- - `url` - URL where error occurred
157
- - `method` - HTTP method
158
- - `status_code` - HTTP status returned
159
- - `user_id` - Optional reference to users table
160
- - `context` - Additional context (params, query)
161
- - `created_at` - Timestamp
162
-
163
- ## Development
164
-
165
- ```bash
166
- # Install dependencies
167
- npm install
168
-
169
- # Run tests
170
- npm run test
171
-
172
- # Run tests without linting
173
- npm run quick:test
174
-
175
- # Build
176
- npm run build
177
-
178
- # Type check
179
- npm run typecheck
180
-
181
- # Lint
182
- npm run lint
183
-
184
- # Format
185
- npm run format
186
- ```
187
-
188
- ## License
189
-
190
- MIT
1
+ # @cepseudo/adonis-audit-log
2
+
3
+ Simple audit logging package for AdonisJS 6.
4
+
5
+ ## Features
6
+
7
+ - Custom audit logs for tracking user actions and business events
8
+ - Automatic HTTP request logging via middleware
9
+ - Error logging with exception handler integration
10
+ - Configurable field sanitization for sensitive data
11
+ - Retention settings for log cleanup
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @cepseudo/adonis-audit-log
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ ```bash
22
+ node ace configure @cepseudo/adonis-audit-log
23
+ ```
24
+
25
+ This will:
26
+
27
+ 1. Publish migrations to `database/migrations/`
28
+ 2. Publish config to `config/audit.ts`
29
+ 3. Register the provider and command in `adonisrc.ts`
30
+
31
+ Then run the migrations:
32
+
33
+ ```bash
34
+ node ace migration:run
35
+ ```
36
+
37
+ ## Configuration Options
38
+
39
+ ```typescript
40
+ // config/audit.ts
41
+ import { defineConfig } from '@cepseudo/adonis-audit-log'
42
+
43
+ export default defineConfig({
44
+ enabled: true,
45
+
46
+ requestLog: {
47
+ enabled: true,
48
+ excludeRoutes: ['/health', '/metrics'],
49
+ excludeMethods: ['OPTIONS'],
50
+ logBody: false,
51
+ logQuery: true,
52
+ sanitizeFields: ['password', 'token', 'secret'],
53
+ },
54
+
55
+ errorLog: {
56
+ enabled: true,
57
+ excludeStatusCodes: [404],
58
+ includeStack: true,
59
+ },
60
+
61
+ auditLog: {
62
+ enabled: true,
63
+ },
64
+
65
+ // Retention in days (0 = unlimited)
66
+ retention: {
67
+ requestLogs: 30,
68
+ errorLogs: 90,
69
+ auditLogs: 0, // Keep forever
70
+ },
71
+ })
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ ### Custom Audit Logs
77
+
78
+ ```typescript
79
+ import audit from '@cepseudo/adonis-audit-log/services/main'
80
+
81
+ // Simple log
82
+ await audit.log('user.login', { user_id: 1 })
83
+
84
+ // With metadata
85
+ await audit.log('submission.create', {
86
+ user_id: auth.user.id,
87
+ submission_id: submission.id,
88
+ project_acronym: submission.projectAcronym,
89
+ })
90
+
91
+ // Track changes
92
+ await audit.logChange('user.update', {
93
+ userId: auth.user.id,
94
+ targetId: targetUser.id,
95
+ changes: {
96
+ email: { from: 'old@example.com', to: 'new@example.com' },
97
+ },
98
+ })
99
+ ```
100
+
101
+ ### Request Logging (Middleware)
102
+
103
+ Add the middleware to your router in `start/kernel.ts`:
104
+
105
+ ```typescript
106
+ router.use([() => import('@cepseudo/adonis-audit-log/middleware/request_logger')])
107
+ ```
108
+
109
+ ### Error Logging (Exception Handler)
110
+
111
+ Integrate with your exception handler in `app/exceptions/handler.ts`:
112
+
113
+ ```typescript
114
+ import { AuditErrorLogger } from '@cepseudo/adonis-audit-log'
115
+
116
+ export default class HttpExceptionHandler extends ExceptionHandler {
117
+ async report(error: unknown, ctx: HttpContext) {
118
+ await AuditErrorLogger.log(error, ctx)
119
+ return super.report(error, ctx)
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Log Cleanup
125
+
126
+ Delete old logs based on retention configuration:
127
+
128
+ ```bash
129
+ node ace audit:cleanup
130
+ ```
131
+
132
+ Set retention to `0` for unlimited retention (logs won't be deleted).
133
+
134
+ ## Database Schema
135
+
136
+ The package creates three tables:
137
+
138
+ ### audit_logs
139
+
140
+ - `id` - Primary key
141
+ - `action_type` - Event type (e.g., 'user.login', 'submission.create')
142
+ - `metadata` - JSON object with additional data
143
+ - `user_id` - Optional reference to users table
144
+ - `created_at` - Timestamp
145
+
146
+ ### request_logs
147
+
148
+ - `id` - Primary key
149
+ - `method` - HTTP method
150
+ - `url` - Request URL
151
+ - `route_name` - Named route if available
152
+ - `status_code` - HTTP response status
153
+ - `response_time_ms` - Response time in milliseconds
154
+ - `ip` - Client IP address
155
+ - `user_agent` - Browser/client user agent
156
+ - `user_id` - Optional reference to users table
157
+ - `request_body` - Sanitized request body (optional)
158
+ - `request_query` - Query parameters
159
+ - `created_at` - Timestamp
160
+
161
+ ### error_logs
162
+
163
+ - `id` - Primary key
164
+ - `error_type` - Error class name
165
+ - `message` - Error message
166
+ - `stack` - Stack trace (optional)
167
+ - `url` - URL where error occurred
168
+ - `method` - HTTP method
169
+ - `status_code` - HTTP status returned
170
+ - `user_id` - Optional reference to users table
171
+ - `context` - Additional context (params, query)
172
+ - `created_at` - Timestamp
173
+
174
+ ## Development
175
+
176
+ ```bash
177
+ # Install dependencies
178
+ npm install
179
+
180
+ # Run tests
181
+ npm run test
182
+
183
+ # Run tests without linting
184
+ npm run quick:test
185
+
186
+ # Build
187
+ npm run build
188
+
189
+ # Type check
190
+ npm run typecheck
191
+
192
+ # Lint
193
+ npm run lint
194
+
195
+ # Format
196
+ npm run format
197
+ ```
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,8 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { CommandOptions } from '@adonisjs/core/types/ace';
3
+ export default class AuditCleanup extends BaseCommand {
4
+ static commandName: string;
5
+ static description: string;
6
+ static options: CommandOptions;
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,64 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Audit Cleanup Command
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Command to delete old audit logs based on retention configuration.
7
+ | Run with: node ace audit:cleanup
8
+ |
9
+ */
10
+ import { BaseCommand } from '@adonisjs/core/ace';
11
+ import { DateTime } from 'luxon';
12
+ export default class AuditCleanup extends BaseCommand {
13
+ static commandName = 'audit:cleanup';
14
+ static description = 'Delete old audit logs based on retention configuration';
15
+ static options = {
16
+ startApp: true,
17
+ };
18
+ async run() {
19
+ const { default: AuditLog } = await import('../src/models/audit_log.js');
20
+ const { default: RequestLog } = await import('../src/models/request_log.js');
21
+ const { default: ErrorLog } = await import('../src/models/error_log.js');
22
+ const config = this.app.config.get('audit');
23
+ if (!config) {
24
+ this.logger.error('Audit config not found. Make sure config/audit.ts exists.');
25
+ return;
26
+ }
27
+ const retention = config.retention;
28
+ let totalDeleted = 0;
29
+ // Cleanup audit logs
30
+ if (retention.auditLogs > 0) {
31
+ const cutoff = DateTime.now().minus({ days: retention.auditLogs });
32
+ const deleted = await AuditLog.query().where('createdAt', '<', cutoff.toJSDate()).delete();
33
+ const count = Array.isArray(deleted) ? deleted.length : deleted;
34
+ this.logger.info(`Deleted ${count} audit logs older than ${retention.auditLogs} days`);
35
+ totalDeleted += count;
36
+ }
37
+ else {
38
+ this.logger.info('Audit logs: retention unlimited (skipped)');
39
+ }
40
+ // Cleanup request logs
41
+ if (retention.requestLogs > 0) {
42
+ const cutoff = DateTime.now().minus({ days: retention.requestLogs });
43
+ const deleted = await RequestLog.query().where('createdAt', '<', cutoff.toJSDate()).delete();
44
+ const count = Array.isArray(deleted) ? deleted.length : deleted;
45
+ this.logger.info(`Deleted ${count} request logs older than ${retention.requestLogs} days`);
46
+ totalDeleted += count;
47
+ }
48
+ else {
49
+ this.logger.info('Request logs: retention unlimited (skipped)');
50
+ }
51
+ // Cleanup error logs
52
+ if (retention.errorLogs > 0) {
53
+ const cutoff = DateTime.now().minus({ days: retention.errorLogs });
54
+ const deleted = await ErrorLog.query().where('createdAt', '<', cutoff.toJSDate()).delete();
55
+ const count = Array.isArray(deleted) ? deleted.length : deleted;
56
+ this.logger.info(`Deleted ${count} error logs older than ${retention.errorLogs} days`);
57
+ totalDeleted += count;
58
+ }
59
+ else {
60
+ this.logger.info('Error logs: retention unlimited (skipped)');
61
+ }
62
+ this.logger.success(`Cleanup complete. Total deleted: ${totalDeleted}`);
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "commands": [
3
+ {
4
+ "commandName": "audit:cleanup",
5
+ "description": "Delete old audit logs based on retention configuration",
6
+ "help": "",
7
+ "namespace": "audit",
8
+ "aliases": [],
9
+ "flags": [],
10
+ "args": [],
11
+ "options": {
12
+ "startApp": true
13
+ },
14
+ "filePath": "./audit_cleanup.js"
15
+ }
16
+ ],
17
+ "version": 1
18
+ }
@@ -0,0 +1,9 @@
1
+ import type { CommandMetaData } from '@adonisjs/core/types/ace';
2
+ /**
3
+ * Reads the commands from the "./commands.json" file.
4
+ */
5
+ export declare function getMetaData(): Promise<CommandMetaData[]>;
6
+ /**
7
+ * Imports the command by looking up its path from the commands metadata
8
+ */
9
+ export declare function getCommand(metaData: CommandMetaData): Promise<typeof import('./audit_cleanup.js').default | null>;
@@ -0,0 +1,37 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Commands Loader
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | This file exports the getMetaData and getCommand functions required
7
+ | by AdonisJS to load commands from packages.
8
+ |
9
+ */
10
+ import { readFile } from 'node:fs/promises';
11
+ /**
12
+ * In-memory cache of commands after they have been loaded
13
+ */
14
+ let commandsMetaData;
15
+ /**
16
+ * Reads the commands from the "./commands.json" file.
17
+ */
18
+ export async function getMetaData() {
19
+ if (commandsMetaData) {
20
+ return commandsMetaData;
21
+ }
22
+ const commandsIndex = await readFile(new URL('./commands.json', import.meta.url), 'utf-8');
23
+ commandsMetaData = JSON.parse(commandsIndex).commands;
24
+ return commandsMetaData;
25
+ }
26
+ /**
27
+ * Imports the command by looking up its path from the commands metadata
28
+ */
29
+ export async function getCommand(metaData) {
30
+ const commands = await getMetaData();
31
+ const command = commands.find(({ commandName }) => metaData.commandName === commandName);
32
+ if (!command) {
33
+ return null;
34
+ }
35
+ const { default: commandConstructor } = await import(new URL(command.filePath, import.meta.url).href);
36
+ return commandConstructor;
37
+ }
@@ -12,47 +12,32 @@ export async function configure(command) {
12
12
  const codemods = await command.createCodemods();
13
13
  // Publish config file
14
14
  await codemods.makeUsingStub(stubsRoot, 'config.stub', {});
15
- // Publish migrations
16
- const now = new Date();
17
- const timestamp = now
18
- .toISOString()
19
- .replace(/[-:]/g, '')
20
- .replace('T', '_')
21
- .replace(/\.\d{3}Z$/, '');
15
+ // Publish migrations using Unix timestamp in milliseconds (AdonisJS standard format)
16
+ const timestamp = Date.now();
22
17
  await codemods.makeUsingStub(stubsRoot, 'migrations/create_audit_logs_table.stub', {
23
18
  migration: {
24
19
  folder: 'database/migrations',
25
20
  fileName: `${timestamp}_create_audit_logs_table.ts`,
26
21
  },
27
22
  });
28
- // Add 1 second to timestamp for ordering
29
- const timestamp2 = new Date(now.getTime() + 1000)
30
- .toISOString()
31
- .replace(/[-:]/g, '')
32
- .replace('T', '_')
33
- .replace(/\.\d{3}Z$/, '');
34
23
  await codemods.makeUsingStub(stubsRoot, 'migrations/create_request_logs_table.stub', {
35
24
  migration: {
36
25
  folder: 'database/migrations',
37
- fileName: `${timestamp2}_create_request_logs_table.ts`,
26
+ fileName: `${timestamp + 1}_create_request_logs_table.ts`,
38
27
  },
39
28
  });
40
- // Add 2 seconds to timestamp for ordering
41
- const timestamp3 = new Date(now.getTime() + 2000)
42
- .toISOString()
43
- .replace(/[-:]/g, '')
44
- .replace('T', '_')
45
- .replace(/\.\d{3}Z$/, '');
46
29
  await codemods.makeUsingStub(stubsRoot, 'migrations/create_error_logs_table.stub', {
47
30
  migration: {
48
31
  folder: 'database/migrations',
49
- fileName: `${timestamp3}_create_error_logs_table.ts`,
32
+ fileName: `${timestamp + 2}_create_error_logs_table.ts`,
50
33
  },
51
34
  });
52
- // Register provider in adonisrc.ts
35
+ // Register provider and command in adonisrc.ts
53
36
  await codemods.updateRcFile((rcFile) => {
54
37
  rcFile.addProvider('@cepseudo/adonis-audit-log/providers/audit_provider');
38
+ rcFile.addCommand('@cepseudo/adonis-audit-log/commands');
55
39
  });
56
40
  command.logger.success('Audit log package configured successfully!');
57
41
  command.logger.info('Run "node ace migration:run" to create the audit tables.');
42
+ command.logger.info('Use "node ace audit:cleanup" to delete old logs based on retention config.');
58
43
  }
@@ -1,5 +1,8 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
2
  import type { NextFn } from '@adonisjs/core/types/http';
3
+ import type { AuditConfig } from '../src/types.js';
3
4
  export default class RequestLoggerMiddleware {
5
+ #private;
6
+ constructor(config: AuditConfig);
4
7
  handle(ctx: HttpContext, next: NextFn): Promise<void>;
5
8
  }
@@ -7,27 +7,20 @@
7
7
  | middleware stack in start/kernel.ts.
8
8
  |
9
9
  */
10
- import app from '@adonisjs/core/services/app';
11
10
  import { RequestLogger } from '../src/request_logger.js';
12
- let requestLogger = null;
13
11
  export default class RequestLoggerMiddleware {
12
+ #requestLogger;
13
+ constructor(config) {
14
+ this.#requestLogger = new RequestLogger(config);
15
+ }
14
16
  async handle(ctx, next) {
15
17
  const startTime = performance.now();
16
18
  // Execute the request
17
19
  await next();
18
20
  // Log the request after response (non-blocking)
19
21
  try {
20
- if (!requestLogger) {
21
- const config = app.config.get('audit');
22
- if (config) {
23
- requestLogger = new RequestLogger(config);
24
- }
25
- }
26
- if (requestLogger) {
27
- const responseTimeMs = Math.round(performance.now() - startTime);
28
- // Use async version to not block the response
29
- requestLogger.logAsync(ctx, responseTimeMs);
30
- }
22
+ const responseTimeMs = Math.round(performance.now() - startTime);
23
+ this.#requestLogger.logAsync(ctx, responseTimeMs);
31
24
  }
32
25
  catch (error) {
33
26
  console.error('[RequestLoggerMiddleware] Error:', error);