Renewal Token Update

This commit is contained in:
Josef 2025-11-24 15:25:10 +01:00
parent d8ebe48a76
commit be4d6d9392
12 changed files with 118 additions and 12 deletions

View File

@ -5,4 +5,5 @@ export class Token
id:string; id:string;
expires:ISOTimeStamp; expires:ISOTimeStamp;
hashedIP:string; hashedIP:string;
renewalToken:string;
} }

View File

@ -1,6 +1,7 @@
import { DateHelper } from "../../browser/date/DateHelper"; import { DateHelper } from "../../browser/date/DateHelper";
import { DateMath } from "../../browser/date/DateMath"; import { DateMath } from "../../browser/date/DateMath";
import { ISOTimeStamp } from "../../browser/date/ISOTimeStamp"; import { ISOTimeStamp } from "../../browser/date/ISOTimeStamp";
import { Message } from "../../browser/messages/Message";
import { CryptIO } from "../crypt/CryptIO"; import { CryptIO } from "../crypt/CryptIO";
import { Token } from "./Token"; import { Token } from "./Token";
import { UserManagementServer } from "./UserManagementServer"; import { UserManagementServer } from "./UserManagementServer";
@ -28,6 +29,7 @@ export class TokenDB
let token = new Token(); let token = new Token();
token.id = CryptIO.createUUID(); token.id = CryptIO.createUUID();
token.renewalToken = CryptIO.createUUID();
token.hashedIP = await CryptIO.hash( ip ); token.hashedIP = await CryptIO.hash( ip );
token.expires = ISOTimeStamp.fromDate( DateMath.fromNowAddMinutes( expireDurationInMinutes ) ); token.expires = ISOTimeStamp.fromDate( DateMath.fromNowAddMinutes( expireDurationInMinutes ) );
@ -36,15 +38,20 @@ export class TokenDB
return Promise.resolve( token ); return Promise.resolve( token );
} }
async validate( token:Token, ip:string ):Promise<boolean>
async validate( token:Token, ip:string, debugMesssges:Message[] = undefined ):Promise<boolean>
{ {
if ( this._tokens.get( token.id ) != token ) if ( this._tokens.get( token.id ) != token )
{ {
debugMesssges?.push( Message.Error( "Token doesn't exist" ) );
return Promise.resolve( false ); return Promise.resolve( false );
} }
if ( DateMath.isExpired( ISOTimeStamp.toDate( token.expires ) ) ) if ( DateMath.isExpired( ISOTimeStamp.toDate( token.expires ) ) )
{ {
debugMesssges?.push( Message.Error( "Token is out of date" ) );
return Promise.resolve( false ); return Promise.resolve( false );
} }
@ -55,6 +62,8 @@ export class TokenDB
return true; return true;
} }
debugMesssges?.push( Message.Error( "Ip mismatch" ) );
return Promise.resolve( false ); return Promise.resolve( false );
} }
} }

View File

@ -156,6 +156,24 @@ export class UserDB
return Promise.resolve( isValid ); return Promise.resolve( isValid );
} }
async renew( oldToken:string, renewal:string, ip:string ):Promise<Token>
{
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<Token> async login( email:string, password:string, ip:string ):Promise<Token>
{ {

View File

@ -10,6 +10,7 @@ export class UserLoginData
timeStamp:ISOTimeStamp; timeStamp:ISOTimeStamp;
location:LocationData; location:LocationData;
userAgent:string; userAgent:string;
renewal:boolean
} }
export class UserData export class UserData

View File

@ -61,6 +61,7 @@ export class ReminderApp extends UserApp<ReminderData> implements iTaskScheduler
( me )=> ( me )=>
{ {
let date = DateHelper.parseDateExpression( me.date ); let date = DateHelper.parseDateExpression( me.date );
date = DateMath.addMinutes( date, me.utcOffsetMinutes );
if ( DateMath.isBefore( date, maxDate ) ) if ( DateMath.isBefore( date, maxDate ) )
{ {

View File

@ -9,6 +9,7 @@ import { ReminderEntry, ReminderEntryType } from "./ReminderEntry";
export class MailEntry extends ReminderEntry export class MailEntry extends ReminderEntry
{ {
date:string; date:string;
utcOffsetMinutes:number;
subject:string; subject:string;
message:string; message:string;

View File

@ -4,6 +4,7 @@ import { ConfirmSignUpHandler } from "./_/confirm-signup";
import { InfoHandler } from "./_/info"; import { InfoHandler } from "./_/info";
import { LoginHandler } from "./_/login"; import { LoginHandler } from "./_/login";
import { LogoutHandler } from "./_/logout"; import { LogoutHandler } from "./_/logout";
import { RenewHandler } from "./_/renew";
import { RequestPasswordChangeHandler } from "./_/request-password-change"; import { RequestPasswordChangeHandler } from "./_/request-password-change";
import { SignUpHandler } from "./_/signup"; import { SignUpHandler } from "./_/signup";
import { AppsCanUseHandler } from "./apps/can-use"; import { AppsCanUseHandler } from "./apps/can-use";
@ -21,6 +22,7 @@ export class HandlerGroups
new ConfirmSignUpHandler(), new ConfirmSignUpHandler(),
new LoginHandler(), new LoginHandler(),
new RenewHandler(),
new LogoutHandler(), new LogoutHandler(),
new InfoHandler(), new InfoHandler(),

View File

@ -38,6 +38,7 @@ export class LoginHandler extends RequestHandler
loginData.timeStamp = ISOTimeStamp.now(); loginData.timeStamp = ISOTimeStamp.now();
loginData.location = await this.getLocation(); loginData.location = await this.getLocation();
loginData.userAgent = this.userAgent; loginData.userAgent = this.userAgent;
loginData.renewal = false;
user.lastLogins = user.lastLogins || []; user.lastLogins = user.lastLogins || [];
user.lastLogins.push( loginData ); user.lastLogins.push( loginData );
@ -56,7 +57,7 @@ export class LoginHandler extends RequestHandler
await this.ums.saveSessionData(); await this.ums.saveSessionData();
return this.sendDataInfo( "Login successfull", { token: session.token } ); return this.sendDataInfo( "Login successfull", { token: session.token, renewal: loginToken.renewalToken } );
} }

View File

@ -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 } );
}
}

View File

@ -22,16 +22,18 @@ export class UserIsLoggedIn extends RequestRequirement
if ( ! session ) 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 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 ) 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(); return this.giveOK();

View File

@ -56,13 +56,13 @@ export class Scheduler
scheduleTasks( tasks:Task[] ) scheduleTasks( tasks:Task[] )
{ {
let maxDate = DateMath.fromNowAddDays( this._maxScheduleDurationDays ); 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 ); this._queue = this._queue.concat( tasks );
tasks.forEach( t => this._scheduledTasks.add( t.id ) ); 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 ]; let newNextTask = this._queue[ 0 ];
@ -98,9 +98,9 @@ export class Scheduler
let task = this._queue[ 0 ]; let task = this._queue[ 0 ];
this._nextTaskID = task.id; 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( this._taskTimerCallback = setTimeout(
() => () =>

View File

@ -8,7 +8,7 @@ export class Task
id:string; id:string;
userID:string; userID:string;
appID:string; appID:string;
date:Date; utcDate:Date;
action:()=>void; action:()=>void;
setUserContext( appID:string, userID:string ) setUserContext( appID:string, userID:string )
@ -25,13 +25,13 @@ export class Task
return Task.createAt( id, date, action ); 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(); let task = new Task();
task.id = id; task.id = id;
task.action = action; task.action = action;
task.date = date; task.utcDate = utcDate;
return task; return task;
} }