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 @@ -