Skip to content

Commit 5626fa6

Browse files
committed
dev-demo: add log loginHistory from user sessions plugin
1 parent 097811d commit 5626fa6

5 files changed

Lines changed: 86 additions & 0 deletions

File tree

‎dev-demo/globalPlugins.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ export const globalPlugins = [
4848
onNewLogin: async ({ username, reasons, revokePath }) => {
4949
console.log(`New sign-in of ${username} (${reasons.join(', ')}), not you? http://localhost:3123${revokePath}`);
5050
},
51+
loginHistory: {
52+
resourceId: 'login_history',
53+
// short to see failed attempts in the list without waiting for a minute
54+
failedLogsInterval: '10s',
55+
resourceColumns: {
56+
userIdColumnName: 'user_id',
57+
successColumnName: 'success',
58+
attemptsColumnName: 'attempts',
59+
ipColumnName: 'ip',
60+
countryColumnName: 'country',
61+
userAgentColumnName: 'user_agent',
62+
ipsColumnName: 'ips',
63+
createdAtColumnName: 'created_at',
64+
},
65+
},
5166
}),
5267
new DashboardPlugin({
5368
dashboardConfigsResourceId: 'dashboard_configs',

‎dev-demo/index.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import background_jobs_resource, { EXAMPLE_JOB_HANDLER_NAME, startExampleBackgro
2828
import BackgroundJobsPlugin from '../plugins/adminforth-background-jobs/index.js';
2929

3030
import auditLogsResource from "./resources/auditLogs.js"
31+
import loginHistoryResource from './resources/loginHistory.js';
3132
import sessionsResource from "./resources/agent_resources/sessions.js";
3233
import dashboardConfigsResource from './resources/dashboard_configs.js';
3334
import turnsResource from './resources/agent_resources/turns.js';
@@ -179,6 +180,7 @@ export const admin = new AdminForth({
179180
resources: [
180181
usersResource,
181182
auditLogsResource,
183+
loginHistoryResource,
182184
cars_SQLITE_resource,
183185
cars_MyS_resource,
184186
cars_PG_resource,
@@ -269,6 +271,11 @@ export const admin = new AdminForth({
269271
icon: 'flowbite:search-outline',
270272
resourceId: 'audit_logs',
271273
},
274+
{
275+
label: 'Login History',
276+
icon: 'lucide:log-in',
277+
resourceId: 'login_history',
278+
},
272279
{
273280
label: 'Translations',
274281
icon: 'material-symbols:translate',
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- CreateTable
2+
CREATE TABLE "login_history" (
3+
"id" TEXT NOT NULL PRIMARY KEY,
4+
"created_at" DATETIME NOT NULL,
5+
"user_id" TEXT NOT NULL,
6+
"success" BOOLEAN NOT NULL,
7+
"attempts" INTEGER NOT NULL,
8+
"ip" TEXT,
9+
"country" TEXT,
10+
"user_agent" TEXT,
11+
"ips" TEXT
12+
);
13+
14+
-- CreateIndex
15+
CREATE INDEX "login_history_user_id_success_created_at_idx" ON "login_history"("user_id", "success", "created_at");

‎dev-demo/migrations/prisma/sqlite/schema.prisma‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ model audit_logs {
5757
executed_by String?
5858
}
5959

60+
model login_history {
61+
id String @id
62+
created_at DateTime
63+
user_id String
64+
success Boolean
65+
attempts Int
66+
ip String?
67+
country String?
68+
user_agent String?
69+
ips String?
70+
71+
@@index([user_id, success, created_at])
72+
}
73+
6074
model mcp_auth_secrets {
6175
id String @id
6276
name String

‎dev-demo/resources/loginHistory.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { AdminForthDataTypes, AdminForthSortDirections } from 'adminforth';
2+
import type { AdminForthResourceInput } from 'adminforth';
3+
import { randomUUID } from 'crypto';
4+
import AutoRemovePlugin from '../../plugins/adminforth-auto-remove/index.js';
5+
6+
export default {
7+
dataSource: 'sqlite',
8+
table: 'login_history',
9+
resourceId: 'login_history',
10+
label: 'Login History',
11+
columns: [
12+
{ name: 'id', primaryKey: true, fillOnCreate: () => randomUUID(), showIn: { all: false } },
13+
{ name: 'created_at', type: AdminForthDataTypes.DATETIME },
14+
{ name: 'user_id', foreignResource: { resourceId: 'adminuser' } },
15+
{ name: 'success', type: AdminForthDataTypes.BOOLEAN },
16+
{ name: 'attempts', type: AdminForthDataTypes.INTEGER },
17+
{ name: 'ip', required: false },
18+
{ name: 'country', required: false },
19+
{ name: 'user_agent', required: false, showIn: { list: false } },
20+
// substring search finds failed attempts by an IP inside the JSON
21+
{ name: 'ips', type: AdminForthDataTypes.JSON, required: false, filterOptions: { substringSearch: true } },
22+
],
23+
options: {
24+
allowedActions: { create: false, edit: false, delete: false },
25+
defaultSort: { columnName: 'created_at', direction: AdminForthSortDirections.desc },
26+
},
27+
plugins: [
28+
new AutoRemovePlugin({
29+
createdAtField: 'created_at',
30+
mode: 'time-based',
31+
deleteOlderThan: '3mo',
32+
interval: '1d',
33+
}),
34+
],
35+
} as AdminForthResourceInput;

0 commit comments

Comments
 (0)