From e633b5bcad9db6ac080ba40c6ef5d33fb9d459bb Mon Sep 17 00:00:00 2001 From: Maksym Pipkun Date: Thu, 12 Jun 2025 14:16:46 +0300 Subject: [PATCH 1/3] feat: add admin user cleanup warning --- adminforth/modules/restApi.ts | 7 +++++-- adminforth/spa/src/App.vue | 25 ++++++++++++++++++++++--- adminforth/types/Back.ts | 8 ++++++++ adminforth/types/Common.ts | 2 ++ dev-demo/index.ts | 16 ++++++++++++++++ dev-demo/resources/users.ts | 2 +- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/adminforth/modules/restApi.ts b/adminforth/modules/restApi.ts index 499ee606..577712ab 100644 --- a/adminforth/modules/restApi.ts +++ b/adminforth/modules/restApi.ts @@ -250,7 +250,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { const usernameColumn = userResource.columns.find((col) => col.name === usernameField); const userPk = dbUser[userResource.columns.find((col) => col.primaryKey).name]; - + const adminforthUserExists = await this.adminforth.config.auth.adminforthUserExists?.(); const userData = { [this.adminforth.config.auth.usernameField]: username, [this.adminforth.config.auth.userFullNameField]: userFullName, @@ -291,7 +291,8 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { } const announcementBadge: AnnouncementBadgeResponse = this.adminforth.config.customization.announcementBadge?.(adminUser); - + const adminforthUserCleanupWarning: AnnouncementBadgeResponse = this.adminforth.config.customization.adminforthUserCleanupWarning?.(adminUser); + const publicPart = { brandName: this.adminforth.config.customization.brandName, usernameFieldName: usernameColumn.label, @@ -314,6 +315,8 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { title: this.adminforth.config.customization.title, emptyFieldPlaceholder: this.adminforth.config.customization.emptyFieldPlaceholder, announcementBadge, + adminforthUserCleanupWarning, + adminforthUserExists, globalInjections: this.adminforth.config.customization.globalInjections, userFullnameField: this.adminforth.config.auth.userFullNameField, } diff --git a/adminforth/spa/src/App.vue b/adminforth/spa/src/App.vue index 2a42eec6..02b46218 100644 --- a/adminforth/spa/src/App.vue +++ b/adminforth/spa/src/App.vue @@ -165,7 +165,21 @@

- +
+
+ + + {{adminforthUserCleanupWarning.title}} + + +
+

+

+ {{ adminforthUserCleanupWarning.text }} +

+
{ + const badge = coreStore.config?.adminforthUserCleanupWarning; + if (!badge) return null; + if (process.env.NODE_ENV === 'production' || coreStore.adminUser?.dbUser.role !== 'superadmin') return null; + if (!coreStore.config?.adminforthUserExists) return null; + return { ...badge }; +}); initFrontedAPI() createHead() @@ -434,7 +454,6 @@ function closeCTA() { await coreStore.fetchMenuBadges(); adminforth.menu.refreshMenuBadges(); }) - } diff --git a/adminforth/types/Back.ts b/adminforth/types/Back.ts index f3427ff8..f756b0a0 100644 --- a/adminforth/types/Back.ts +++ b/adminforth/types/Back.ts @@ -739,6 +739,13 @@ interface AdminForthInputConfigCustomization { * Execution is done on admin app load. */ announcementBadge?: (user: AdminUser) => AnnouncementBadgeResponse, + + /** + * Function to return custom badge in side bar for users. Can return text or html + * If function is not passed or returns null, badge will not be shown. + * Execution is done on admin app load. + */ + adminforthUserCleanupWarning?: (user: AdminUser) => AnnouncementBadgeResponse, /** * Custom panel components or array of components which will be displayed in the login form @@ -964,6 +971,7 @@ export interface AdminForthInputConfig { */ rememberMeDays?: number, + adminforthUserExists?: () => Promise, /** * Can be used to limit user access when subscribing from frontend to websocket topics. diff --git a/adminforth/types/Common.ts b/adminforth/types/Common.ts index 7385fc33..e2b763c1 100644 --- a/adminforth/types/Common.ts +++ b/adminforth/types/Common.ts @@ -1058,6 +1058,8 @@ export interface AdminForthConfigForFrontend { list?: string, }, announcementBadge?: AnnouncementBadgeResponse | null, + adminforthUserCleanupWarning?: AnnouncementBadgeResponse | null, + adminforthUserExists?: boolean, globalInjections: { userMenu: Array, header: Array, diff --git a/dev-demo/index.ts b/dev-demo/index.ts index d10c4199..f1631943 100644 --- a/dev-demo/index.ts +++ b/dev-demo/index.ts @@ -84,6 +84,9 @@ export const admin = new AdminForth({ beforeLoginConfirmation: [async ({adminUser, adminforth, extra}) => { adminforth.resource('users').update(adminUser.dbUser.id, { last_login_ip: adminforth.auth.getClientIp(extra.headers) }); }], + adminforthUserExists: async (): Promise => { + return !!await admin.resource('users').get([Filters.EQ('email', 'adminforth')]); + }, websocketTopicAuth: async (topic: string, adminUser: AdminUser) => { if (!adminUser) { // don't allow anonymous users to subscribe @@ -176,6 +179,19 @@ export const admin = new AdminForth({ } }, + adminforthUserCleanupWarning: (adminUser: AdminUser) => { + return { + html: ` +

The default admin user adminforth is still active in production.

+

For security reasons, it's strongly recommended to create your own account and delete this default user.

+
+

This action is critical and cannot be undone.

+ `, + closable: false, + title: 'Critical Security Warning', + } + }, + // loginPageInjections: { // underInputs: '@@/login2.vue', // } diff --git a/dev-demo/resources/users.ts b/dev-demo/resources/users.ts index 0c71ece8..b2dee9f2 100644 --- a/dev-demo/resources/users.ts +++ b/dev-demo/resources/users.ts @@ -206,7 +206,7 @@ export default { { name: "role", enum: [ - // { value: 'superadmin', label: 'Super Admin' }, + { value: 'superadmin', label: 'Super Admin' }, { value: "user", label: "User" }, ], }, From f7b98d08e7220b3fd6a0452139bdc8d0d12f3010 Mon Sep 17 00:00:00 2001 From: Maksym Pipkun Date: Thu, 12 Jun 2025 15:17:38 +0300 Subject: [PATCH 2/3] fix: remove adminforthUserExists references and update cleanup warning logic --- adminforth/modules/restApi.ts | 2 -- adminforth/spa/src/App.vue | 15 ++++++++------- adminforth/types/Back.ts | 2 -- adminforth/types/Common.ts | 1 - dev-demo/index.ts | 3 --- dev-demo/resources/users.ts | 2 +- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/adminforth/modules/restApi.ts b/adminforth/modules/restApi.ts index 577712ab..e9a73708 100644 --- a/adminforth/modules/restApi.ts +++ b/adminforth/modules/restApi.ts @@ -250,7 +250,6 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { const usernameColumn = userResource.columns.find((col) => col.name === usernameField); const userPk = dbUser[userResource.columns.find((col) => col.primaryKey).name]; - const adminforthUserExists = await this.adminforth.config.auth.adminforthUserExists?.(); const userData = { [this.adminforth.config.auth.usernameField]: username, [this.adminforth.config.auth.userFullNameField]: userFullName, @@ -316,7 +315,6 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { emptyFieldPlaceholder: this.adminforth.config.customization.emptyFieldPlaceholder, announcementBadge, adminforthUserCleanupWarning, - adminforthUserExists, globalInjections: this.adminforth.config.customization.globalInjections, userFullnameField: this.adminforth.config.auth.userFullNameField, } diff --git a/adminforth/spa/src/App.vue b/adminforth/spa/src/App.vue index 02b46218..c5fe4b8d 100644 --- a/adminforth/spa/src/App.vue +++ b/adminforth/spa/src/App.vue @@ -277,13 +277,7 @@ import adminforth from '@/adminforth'; const coreStore = useCoreStore(); const toastStore = useToastStore(); const userStore = useUserStore(); -const adminforthUserCleanupWarning = computed(() => { - const badge = coreStore.config?.adminforthUserCleanupWarning; - if (!badge) return null; - if (process.env.NODE_ENV === 'production' || coreStore.adminUser?.dbUser.role !== 'superadmin') return null; - if (!coreStore.config?.adminforthUserExists) return null; - return { ...badge }; -}); + initFrontedAPI() createHead() @@ -456,5 +450,12 @@ function closeCTA() { }) } +const adminforthUserCleanupWarning = computed(() => { + const badge = coreStore.config?.adminforthUserCleanupWarning; + if (!badge) return null; + if (process.env.NODE_ENV !== 'production' || coreStore.adminUser?.dbUser.email !== 'adminforth') return null; + return { ...badge }; +}); + diff --git a/adminforth/types/Back.ts b/adminforth/types/Back.ts index f756b0a0..4c1e1165 100644 --- a/adminforth/types/Back.ts +++ b/adminforth/types/Back.ts @@ -971,8 +971,6 @@ export interface AdminForthInputConfig { */ rememberMeDays?: number, - adminforthUserExists?: () => Promise, - /** * Can be used to limit user access when subscribing from frontend to websocket topics. * @param topic - topic where user is trying to subscribe diff --git a/adminforth/types/Common.ts b/adminforth/types/Common.ts index e2b763c1..4ffcdde7 100644 --- a/adminforth/types/Common.ts +++ b/adminforth/types/Common.ts @@ -1059,7 +1059,6 @@ export interface AdminForthConfigForFrontend { }, announcementBadge?: AnnouncementBadgeResponse | null, adminforthUserCleanupWarning?: AnnouncementBadgeResponse | null, - adminforthUserExists?: boolean, globalInjections: { userMenu: Array, header: Array, diff --git a/dev-demo/index.ts b/dev-demo/index.ts index f1631943..3395e474 100644 --- a/dev-demo/index.ts +++ b/dev-demo/index.ts @@ -84,9 +84,6 @@ export const admin = new AdminForth({ beforeLoginConfirmation: [async ({adminUser, adminforth, extra}) => { adminforth.resource('users').update(adminUser.dbUser.id, { last_login_ip: adminforth.auth.getClientIp(extra.headers) }); }], - adminforthUserExists: async (): Promise => { - return !!await admin.resource('users').get([Filters.EQ('email', 'adminforth')]); - }, websocketTopicAuth: async (topic: string, adminUser: AdminUser) => { if (!adminUser) { // don't allow anonymous users to subscribe diff --git a/dev-demo/resources/users.ts b/dev-demo/resources/users.ts index b2dee9f2..0c71ece8 100644 --- a/dev-demo/resources/users.ts +++ b/dev-demo/resources/users.ts @@ -206,7 +206,7 @@ export default { { name: "role", enum: [ - { value: 'superadmin', label: 'Super Admin' }, + // { value: 'superadmin', label: 'Super Admin' }, { value: "user", label: "User" }, ], }, From 823d4d8abd03f6091098508deb31b57ff111086d Mon Sep 17 00:00:00 2001 From: Maksym Pipkun Date: Thu, 12 Jun 2025 17:39:59 +0300 Subject: [PATCH 3/3] update dev demo admin user creation logic --- dev-demo/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-demo/index.ts b/dev-demo/index.ts index 3395e474..d3cee0c3 100644 --- a/dev-demo/index.ts +++ b/dev-demo/index.ts @@ -519,8 +519,8 @@ admin.express.serve(app); admin.discoverDatabases().then(async () => { console.log('🅿️ Database discovered'); - if (!await admin.resource('users').get([Filters.EQ('email', 'adminforth')])) { - await admin.resource('users').create({ + if (await admin.resource('adminuser').count() === 0) { + await admin.resource('adminuser').create({ email: 'adminforth', password_hash: await AdminForth.Utils.generatePasswordHash('adminforth'), role: 'superadmin',