From be4d6d9392ae6494cfb9c9e853c78615f4753aa6 Mon Sep 17 00:00:00 2001 From: Josef Date: Mon, 24 Nov 2025 15:25:10 +0100 Subject: [PATCH] Renewal Token Update --- node/users/Token.ts | 1 + node/users/TokenDB.ts | 11 ++- node/users/UserDB.ts | 18 +++++ node/users/UserData.ts | 1 + node/users/apps/reminder/ReminderApp.ts | 1 + node/users/apps/reminder/data/MailEntry.ts | 1 + node/users/handlers/HandlerGroups.ts | 2 + node/users/handlers/_/login.ts | 3 +- node/users/handlers/_/renew.ts | 70 +++++++++++++++++++ .../users/requirements/user/UserIsLoggedIn.ts | 8 ++- node/users/scheduler/Scheduler.ts | 8 +-- node/users/scheduler/Task.ts | 6 +- 12 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 node/users/handlers/_/renew.ts diff --git a/node/users/Token.ts b/node/users/Token.ts index 170f2ac..0c0f2c2 100644 --- a/node/users/Token.ts +++ b/node/users/Token.ts @@ -5,4 +5,5 @@ export class Token id:string; expires:ISOTimeStamp; hashedIP:string; + renewalToken:string; } \ No newline at end of file diff --git a/node/users/TokenDB.ts b/node/users/TokenDB.ts index 2ef0b9f..07c716f 100644 --- a/node/users/TokenDB.ts +++ b/node/users/TokenDB.ts @@ -1,6 +1,7 @@ import { DateHelper } from "../../browser/date/DateHelper"; import { DateMath } from "../../browser/date/DateMath"; import { ISOTimeStamp } from "../../browser/date/ISOTimeStamp"; +import { Message } from "../../browser/messages/Message"; import { CryptIO } from "../crypt/CryptIO"; import { Token } from "./Token"; import { UserManagementServer } from "./UserManagementServer"; @@ -28,6 +29,7 @@ export class TokenDB let token = new Token(); token.id = CryptIO.createUUID(); + token.renewalToken = CryptIO.createUUID(); token.hashedIP = await CryptIO.hash( ip ); token.expires = ISOTimeStamp.fromDate( DateMath.fromNowAddMinutes( expireDurationInMinutes ) ); @@ -36,15 +38,20 @@ export class TokenDB return Promise.resolve( token ); } - async validate( token:Token, ip:string ):Promise + + + async validate( token:Token, ip:string, debugMesssges:Message[] = undefined ):Promise { if ( this._tokens.get( token.id ) != token ) { + debugMesssges?.push( Message.Error( "Token doesn't exist" ) ); + return Promise.resolve( false ); } if ( DateMath.isExpired( ISOTimeStamp.toDate( token.expires ) ) ) { + debugMesssges?.push( Message.Error( "Token is out of date" ) ); return Promise.resolve( false ); } @@ -55,6 +62,8 @@ export class TokenDB return true; } + debugMesssges?.push( Message.Error( "Ip mismatch" ) ); + return Promise.resolve( false ); } } \ No newline at end of file diff --git a/node/users/UserDB.ts b/node/users/UserDB.ts index 893d369..58641a9 100644 --- a/node/users/UserDB.ts +++ b/node/users/UserDB.ts @@ -156,6 +156,24 @@ export class UserDB return Promise.resolve( isValid ); } + async renew( oldToken:string, renewal:string, ip:string ):Promise + { + let session = this._ums._sessions.get( oldToken ); + let tokenData = this._ums._tokenDB._tokens.get( session.token ); + + if ( tokenData.renewalToken !== renewal ) + { + return Promise.resolve( null ); + } + + let hour = 60; + let day = hour * 24; + let week = day * 7; + + let token = await this._ums.tokenDB.create( ip, week ); + + return Promise.resolve( token ); + } async login( email:string, password:string, ip:string ):Promise { diff --git a/node/users/UserData.ts b/node/users/UserData.ts index c272887..ecd88cd 100644 --- a/node/users/UserData.ts +++ b/node/users/UserData.ts @@ -10,6 +10,7 @@ export class UserLoginData timeStamp:ISOTimeStamp; location:LocationData; userAgent:string; + renewal:boolean } export class UserData diff --git a/node/users/apps/reminder/ReminderApp.ts b/node/users/apps/reminder/ReminderApp.ts index f4b3a35..d46a33c 100644 --- a/node/users/apps/reminder/ReminderApp.ts +++ b/node/users/apps/reminder/ReminderApp.ts @@ -61,6 +61,7 @@ export class ReminderApp extends UserApp implements iTaskScheduler ( me )=> { let date = DateHelper.parseDateExpression( me.date ); + date = DateMath.addMinutes( date, me.utcOffsetMinutes ); if ( DateMath.isBefore( date, maxDate ) ) { diff --git a/node/users/apps/reminder/data/MailEntry.ts b/node/users/apps/reminder/data/MailEntry.ts index 2a49369..5844b52 100644 --- a/node/users/apps/reminder/data/MailEntry.ts +++ b/node/users/apps/reminder/data/MailEntry.ts @@ -9,6 +9,7 @@ import { ReminderEntry, ReminderEntryType } from "./ReminderEntry"; export class MailEntry extends ReminderEntry { date:string; + utcOffsetMinutes:number; subject:string; message:string; diff --git a/node/users/handlers/HandlerGroups.ts b/node/users/handlers/HandlerGroups.ts index 1adf0c1..0494ee9 100644 --- a/node/users/handlers/HandlerGroups.ts +++ b/node/users/handlers/HandlerGroups.ts @@ -4,6 +4,7 @@ import { ConfirmSignUpHandler } from "./_/confirm-signup"; import { InfoHandler } from "./_/info"; import { LoginHandler } from "./_/login"; import { LogoutHandler } from "./_/logout"; +import { RenewHandler } from "./_/renew"; import { RequestPasswordChangeHandler } from "./_/request-password-change"; import { SignUpHandler } from "./_/signup"; import { AppsCanUseHandler } from "./apps/can-use"; @@ -21,6 +22,7 @@ export class HandlerGroups new ConfirmSignUpHandler(), new LoginHandler(), + new RenewHandler(), new LogoutHandler(), new InfoHandler(), diff --git a/node/users/handlers/_/login.ts b/node/users/handlers/_/login.ts index 3515bec..c30635f 100644 --- a/node/users/handlers/_/login.ts +++ b/node/users/handlers/_/login.ts @@ -38,6 +38,7 @@ export class LoginHandler extends RequestHandler loginData.timeStamp = ISOTimeStamp.now(); loginData.location = await this.getLocation(); loginData.userAgent = this.userAgent; + loginData.renewal = false; user.lastLogins = user.lastLogins || []; user.lastLogins.push( loginData ); @@ -56,7 +57,7 @@ export class LoginHandler extends RequestHandler await this.ums.saveSessionData(); - return this.sendDataInfo( "Login successfull", { token: session.token } ); + return this.sendDataInfo( "Login successfull", { token: session.token, renewal: loginToken.renewalToken } ); } diff --git a/node/users/handlers/_/renew.ts b/node/users/handlers/_/renew.ts new file mode 100644 index 0000000..1951aab --- /dev/null +++ b/node/users/handlers/_/renew.ts @@ -0,0 +1,70 @@ +import { RegExpUtility } from "../../../../browser/text/RegExpUtitlity"; +import { CryptIO } from "../../../crypt/CryptIO"; +import { RJLog } from "../../../log/RJLog"; +import { RequestHandler, RequestType } from "../../RequestHandler"; +import { FastifyRequest, FastifyReply } from 'fastify'; +import { VariableReplacer, Variables } from "../../../../browser/text/replacing/VariableReplacer"; +import { ConfirmSignUpHandler } from "./confirm-signup"; +import { Session } from "../../Session"; +import { UserLoginData } from "../../UserData"; +import { ISOTimeStamp } from "../../../../browser/date/ISOTimeStamp"; +import { Arrays } from "../../../../browser/tools/Arrays"; + +export class RenewHandler extends RequestHandler +{ + static url = "/renew"; + constructor(){ super( RequestType.POST, RenewHandler.url ); } + + async _handle( request:FastifyRequest, reply:FastifyReply ) + { + let requestBody = request.body; + let { token, renewal } = requestBody as { token: string; renewal: string }; + + if ( ! token || ! renewal ) + { + return this.sendError( "Missing token or renewal:" + `"${requestBody}"` ); + } + + let session = this._ums._sessions.get( token ); + + if ( ! session ) + { + return this.sendError( "Invalid token" ); + } + + let user = await this._ums.userDB.byID( session.userID ); + + let renewToken = await this.userDB.renew( token, renewal, this.ip ); + + if ( ! renewToken ) + { + return this.sendError( "Renewal failed" ); + } + + let loginData = new UserLoginData(); + loginData.timeStamp = ISOTimeStamp.now(); + loginData.location = await this.getLocation(); + loginData.userAgent = this.userAgent; + loginData.renewal = true; + + user.lastLogins = user.lastLogins || []; + user.lastLogins.push( loginData ); + + Arrays.shiftToSize( user.lastLogins, this._ums._settings.maxLogins ); + + this._ums._sessions.delete( session.token ); + + session.token = renewToken.id; + + this._ums._sessions.set( session.token, session ); + + await this.userDB.save(); + + await this.ums.saveSessionData(); + + + return this.sendDataInfo( "Renewal successfull", { token: session.token, renewal: renewToken.renewalToken } ); + } + + +} \ No newline at end of file diff --git a/node/users/requirements/user/UserIsLoggedIn.ts b/node/users/requirements/user/UserIsLoggedIn.ts index 52b903c..f21002f 100644 --- a/node/users/requirements/user/UserIsLoggedIn.ts +++ b/node/users/requirements/user/UserIsLoggedIn.ts @@ -22,16 +22,18 @@ export class UserIsLoggedIn extends RequestRequirement if ( ! session ) { - return this.sendError( "No session for token:" + tokenID ); + return this.sendError( "No session for token: " + tokenID ); } let token = this.ums.tokenDB._tokens.get( tokenID ); - let isValid = await this.ums.tokenDB.validate( token, request.ip ); + let messages = []; + let isValid = await this.ums.tokenDB.validate( token, request.ip, messages ); if ( ! isValid ) { - return this.sendError( "Invalid token" + tokenID ); + let message = messages.length > 0 ? ( " " + messages.join( ", " ) ) : ""; + return this.sendError( "Invalid token: " + tokenID + message ); } return this.giveOK(); diff --git a/node/users/scheduler/Scheduler.ts b/node/users/scheduler/Scheduler.ts index 42602c0..768c341 100644 --- a/node/users/scheduler/Scheduler.ts +++ b/node/users/scheduler/Scheduler.ts @@ -56,13 +56,13 @@ export class Scheduler scheduleTasks( tasks:Task[] ) { let maxDate = DateMath.fromNowAddDays( this._maxScheduleDurationDays ); - tasks = tasks.filter( t => ! this._scheduledTasks.has( t.id ) && ! DateMath.isAfter( t.date, maxDate ) ); + tasks = tasks.filter( t => ! this._scheduledTasks.has( t.id ) && ! DateMath.isAfter( t.utcDate, maxDate ) ); this._queue = this._queue.concat( tasks ); tasks.forEach( t => this._scheduledTasks.add( t.id ) ); - this._queue.sort( ( a, b ) => { return a.date.getTime() - b.date.getTime() } ); + this._queue.sort( ( a, b ) => { return a.utcDate.getTime() - b.utcDate.getTime() } ); let newNextTask = this._queue[ 0 ]; @@ -98,9 +98,9 @@ export class Scheduler let task = this._queue[ 0 ]; this._nextTaskID = task.id; - let delay = Math.max( 0, task.date.getTime() - Date.now() ); + let delay = Math.max( 0, task.utcDate.getTime() - Date.now() ); - RJLog.log( task.id, DateFormatter.HMS( task.date ), "delaying:", Duration.fromMilliSeconds( delay ) + " sec" ); + RJLog.log( task.id, DateFormatter.HMS( task.utcDate ), "delaying:", Duration.fromMilliSeconds( delay ) + " sec" ); this._taskTimerCallback = setTimeout( () => diff --git a/node/users/scheduler/Task.ts b/node/users/scheduler/Task.ts index e9e86aa..5806dd5 100644 --- a/node/users/scheduler/Task.ts +++ b/node/users/scheduler/Task.ts @@ -8,7 +8,7 @@ export class Task id:string; userID:string; appID:string; - date:Date; + utcDate:Date; action:()=>void; setUserContext( appID:string, userID:string ) @@ -25,13 +25,13 @@ export class Task return Task.createAt( id, date, action ); } - static createAt( id:string, date:Date, action:()=>void ) + static createAt( id:string, utcDate:Date, action:()=>void ) { let task = new Task(); task.id = id; task.action = action; - task.date = date; + task.utcDate = utcDate; return task; }