From 48067fd2ed9680b4275846e905641b2e10e3ecb5 Mon Sep 17 00:00:00 2001 From: Vitalii Kulyk Date: Tue, 2 Dec 2025 13:09:15 +0200 Subject: [PATCH] feat: rename 'rememberMeDays' to 'rememberMeDuration' across the codebase and update related logic for session duration handling --- adminforth/auth.ts | 18 ++++--------- .../commands/createApp/templates/index.ts.hbs | 2 +- .../tutorial/03-Customization/12-security.md | 4 +-- adminforth/modules/configValidator.ts | 27 +++++++++++++++++++ adminforth/modules/restApi.ts | 18 +++++++------ adminforth/spa/src/views/LoginView.vue | 4 +-- adminforth/types/Back.ts | 19 +++++++------ adminforth/types/Common.ts | 2 +- 8 files changed, 59 insertions(+), 35 deletions(-) diff --git a/adminforth/auth.ts b/adminforth/auth.ts index ada788fa..233d3356 100644 --- a/adminforth/auth.ts +++ b/adminforth/auth.ts @@ -76,27 +76,19 @@ class AdminForthAuth implements IAdminForthAuth { response.setHeader('Set-Cookie', `adminforth_${brandSlug}_jwt=; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:00 GMT`); } - setAuthCookie({ expireInDays, response, username, pk}: { - expireInDays?: number, + setAuthCookie({ expireInDuration, response, username, pk}: { + expireInDuration?: string, response: any, username: string, pk: string | null }) { - console.log("in days", expireInDays); - const expiresIn: string = expireInDays ? `${expireInDays}d` : (process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h'); - console.log("in string", expiresIn); + const expiresIn: string = expireInDuration || (process.env.ADMINFORTH_AUTH_EXPIRESIN || '24h'); // might be h,m,d in string - const expiresInSec = parseTimeToSeconds(expiresIn); - console.log("expiresInSec", expiresInSec); - - const token = this.issueJWT({ username, pk}, 'auth', expiresIn); - console.log("token", token); + const token = this.issueJWT({ username, pk}, 'auth', expiresInSec); const expiresCookieFormat = new Date(Date.now() + expiresInSec * 1000).toUTCString(); - console.log("expiresCookieFormat", expiresCookieFormat); const brandSlug = this.adminforth.config.customization.brandNameSlug; - console.log("brandSlug", brandSlug); response.setHeader('Set-Cookie', `adminforth_${brandSlug}_jwt=${token}; Path=${this.adminforth.config.baseUrl || '/'}; HttpOnly; SameSite=Strict; Expires=${expiresCookieFormat}`); } @@ -131,7 +123,7 @@ class AdminForthAuth implements IAdminForthAuth { return cookies.find((cookie) => cookie.key === `adminforth_${brandSlug}_${name}`)?.value || null; } - issueJWT(payload: Object, type: string, expiresIn: string = '24h'): string { + issueJWT(payload: Object, type: string, expiresIn: string | number = '24h'): string { // read ADMINFORH_SECRET from environment if not drop error const secret = process.env.ADMINFORTH_SECRET; if (!secret) { diff --git a/adminforth/commands/createApp/templates/index.ts.hbs b/adminforth/commands/createApp/templates/index.ts.hbs index e299f226..ae7722b0 100644 --- a/adminforth/commands/createApp/templates/index.ts.hbs +++ b/adminforth/commands/createApp/templates/index.ts.hbs @@ -14,7 +14,7 @@ export const admin = new AdminForth({ usersResourceId: 'adminuser', usernameField: 'email', passwordHashField: 'password_hash', - rememberMeDays: 30, + rememberMeDuration: '30d', loginBackgroundImage: 'https://images.unsplash.com/photo-1534239697798-120952b76f2b?q=80&w=3389&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', loginBackgroundPosition: '1/2', loginPromptHTML: async () => { diff --git a/adminforth/documentation/docs/tutorial/03-Customization/12-security.md b/adminforth/documentation/docs/tutorial/03-Customization/12-security.md index ca495c10..75eb9332 100644 --- a/adminforth/documentation/docs/tutorial/03-Customization/12-security.md +++ b/adminforth/documentation/docs/tutorial/03-Customization/12-security.md @@ -11,14 +11,14 @@ You can tweak login cookie expiration time by setting environment `ADMINFORTH_AU ADMINFORTH_AUTH_EXPIRESIN=1h ``` -Also you can set `auth.rememberMeDays` in the config to set how long "remember me" logins will last. +Also you can set `auth.rememberMeDuration` in the config to set how long "remember me" logins will last. For example to set it to 7 days: ```ts ./index.ts new AdminForth({ ... auth: { - rememberMeDays: 7 + rememberMeDuration: '7d' // '7d' for 7 days, '24h' for 24 hours, '30m' for 30 minutes, etc. } } ``` diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts index 3ff78e4a..0e4d8871 100644 --- a/adminforth/modules/configValidator.ts +++ b/adminforth/modules/configValidator.ts @@ -1141,6 +1141,33 @@ export default class ConfigValidator implements IConfigValidator { } } + if (newConfig.auth.rememberMeDays !== undefined) { + const rememberMeDays = newConfig.auth.rememberMeDays; + if (typeof rememberMeDays !== 'number' || rememberMeDays <= 0) { + errors.push(`auth.rememberMeDays must be a positive number`); + } else { + if (!newConfig.auth.rememberMeDuration) { + newConfig.auth.rememberMeDuration = `${rememberMeDays}d`; + warnings.push(`⚠️ auth.rememberMeDays is deprecated. Please use auth.rememberMeDuration: "${rememberMeDays}d" instead. Auto-converted for now.`); + } else { + warnings.push(`⚠️ Both auth.rememberMeDays and auth.rememberMeDuration are set. Using rememberMeDuration. Please remove rememberMeDays.`); + } + } + delete newConfig.auth.rememberMeDays; + } + + if (newConfig.auth.rememberMeDuration !== undefined) { + const duration = newConfig.auth.rememberMeDuration; + if (typeof duration !== 'string') { + errors.push(`auth.rememberMeDuration must be a string in format "1s", "1m", "1h", or "1d"`); + } else { + const match = duration.match(/^(\d+)([smhd])$/); + if (!match) { + errors.push(`auth.rememberMeDuration must be in format "1s", "1m", "1h", or "1d" (e.g., "30d" for 30 days), got: "${duration}"`); + } + } + } + // normalize beforeLoginConfirmation hooks const blc = this.inputConfig.auth.beforeLoginConfirmation; if (!Array.isArray(blc)) { diff --git a/adminforth/modules/restApi.ts b/adminforth/modules/restApi.ts index 3940e920..c1567fa5 100644 --- a/adminforth/modules/restApi.ts +++ b/adminforth/modules/restApi.ts @@ -129,7 +129,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { this.adminforth = adminforth; } - async processLoginCallbacks(adminUser: AdminUser, toReturn: { redirectTo?: string, allowedLogin:boolean, error?: string }, response: any, extra: HttpExtra, rememberMeDays?: number) { + async processLoginCallbacks(adminUser: AdminUser, toReturn: { redirectTo?: string, allowedLogin:boolean, error?: string }, response: any, extra: HttpExtra, sessionDuration?: string) { const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation as (BeforeLoginConfirmationFunction[] | undefined); for (const hook of listify(beforeLoginConfirmation)) { @@ -138,7 +138,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { response, adminforth: this.adminforth, extra, - rememberMeDays + sessionDuration, }); if (resp?.body?.redirectTo || resp?.error) { @@ -205,17 +205,19 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { username, }; - const expireInDays = rememberMe ? this.adminforth.config.auth.rememberMeDays || 30 : 1; - + const expireInDuration = rememberMe + ? (this.adminforth.config.auth.rememberMeDuration || '30d') + : '1d'; + console.log('expireInDuration', expireInDuration); await this.processLoginCallbacks(adminUser, toReturn, response, { body, headers, query, cookies, requestUrl, - }, expireInDays); + }, expireInDuration); if (toReturn.allowedLogin) { this.adminforth.auth.setAuthCookie({ - expireInDays, + expireInDuration, response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] @@ -289,7 +291,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { everyPageBottom: this.adminforth.config.customization.globalInjections.everyPageBottom, sidebarTop: this.adminforth.config.customization.globalInjections.sidebarTop, }, - rememberMeDays: this.adminforth.config.auth.rememberMeDays, + rememberMeDuration: this.adminforth.config.auth.rememberMeDuration, singleTheme: this.adminforth.config.customization.singleTheme, customHeadItems: this.adminforth.config.customization.customHeadItems, }; @@ -381,7 +383,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { title: this.adminforth.config.customization?.title, demoCredentials: this.adminforth.config.auth.demoCredentials, loginPageInjections: this.adminforth.config.customization.loginPageInjections, - rememberMeDays: this.adminforth.config.auth.rememberMeDays, + rememberMeDuration: this.adminforth.config.auth.rememberMeDuration, singleTheme: this.adminforth.config.customization.singleTheme, customHeadItems: this.adminforth.config.customization.customHeadItems, } diff --git a/adminforth/spa/src/views/LoginView.vue b/adminforth/spa/src/views/LoginView.vue index eb21391b..7ad87ac3 100644 --- a/adminforth/spa/src/views/LoginView.vue +++ b/adminforth/spa/src/views/LoginView.vue @@ -76,9 +76,9 @@ -
{{ $t('Remember me') }} diff --git a/adminforth/types/Back.ts b/adminforth/types/Back.ts index fdb648d9..35ae6ccc 100644 --- a/adminforth/types/Back.ts +++ b/adminforth/types/Back.ts @@ -303,7 +303,7 @@ export interface IAdminForthDataSourceConnectorConstructor { export interface IAdminForthAuth { verify(jwt : string, mustHaveType: string, decodeUser?: boolean): Promise; - issueJWT(payload: Object, type: string, expiresIn?: string): string; + issueJWT(payload: Object, type: string, expiresIn?: string | number): string; removeCustomCookie({response, name}: {response: any, name: string}): void; @@ -311,7 +311,7 @@ export interface IAdminForthAuth { getCustomCookie({cookies, name}: {cookies: {key: string, value: string}[], name: string}): string | null; - setAuthCookie({expireInDays, response, username, pk,}: {expireInDays?: number, response: any, username: string, pk: string}): void; + setAuthCookie({expireInDuration, response, username, pk,}: {expireInDuration?: string, response: any, username: string, pk: string}): void; removeAuthCookie(response: any): void; @@ -331,8 +331,9 @@ export interface IAdminForthRestAPI { * @param adminUser - plugin/af pases current adminUser * @param toReturn - this is an object which will get status of login process. If at least one callback returns error or redirectTo, login process will be stopped (future callbacks will not be called). * @param response - http response object + * @param sessionDuration - duration of session in format "1s", "1m", "1h", or "1d" (e.g., "30d" for 30 days) */ - processLoginCallbacks(adminUser: AdminUser, toReturn: { redirectTo?: string, allowedLogin: boolean, error?: string }, response: any, extra: HttpExtra): Promise; + processLoginCallbacks(adminUser: AdminUser, toReturn: { redirectTo?: string, allowedLogin: boolean, error?: string }, response: any, extra: HttpExtra, sessionDuration?: string): Promise; } export interface IAdminForth { @@ -605,7 +606,7 @@ export type BeforeLoginConfirmationFunction = (params?: { response: IAdminForthHttpResponse, adminforth: IAdminForth, extra?: HttpExtra, - rememberMeDays?: number, + sessionDuration?: string, }) => Promise<{ error?: string, body: { @@ -1053,11 +1054,13 @@ export interface AdminForthInputConfig { loginPromptHTML?: string | (() => string | void | undefined | Promise) | undefined /** - * Remember me days for "Remember Me" checkbox on login page. - * If not set or set to null/0/undefined, "Remember Me" checkbox will not be displayed. - * If rememberMeDays is set, then users who check "Remember Me" will be staying logged in for this amount of days. + * Remember me duration for "Remember Me" checkbox on login page. + * If not set or set to null/undefined, "Remember Me" checkbox will not be displayed. + * If rememberMeDuration is set, then users who check "Remember Me" will be staying logged in for this amount of time. + * Format: "1s" (seconds), "1m" (minutes), "1h" (hours), or "1d" (days). + * Example: "30d" for 30 days, "7d" for 7 days, "24h" for 24 hours. */ - rememberMeDays?: number, + rememberMeDuration?: string, /** diff --git a/adminforth/types/Common.ts b/adminforth/types/Common.ts index 016fa074..c52df189 100644 --- a/adminforth/types/Common.ts +++ b/adminforth/types/Common.ts @@ -1119,7 +1119,7 @@ export interface AdminForthConfigForFrontend { underInputs: Array, panelHeader: Array, }, - rememberMeDays: number, + rememberMeDuration: string, showBrandNameInSidebar: boolean, showBrandLogoInSidebar: boolean, brandLogo?: string,