import Fastify, { FastifyHttpsOptions, FastifyInstance, FastifyListenOptions, FastifyRequest } from "fastify"; import fastifyMultipart from "@fastify/multipart"; import cors from '@fastify/cors'; import { EventSlot } from "../../browser/events/EventSlot"; import { JSRandomEngine } from "../../browser/random/JSRandomEngine"; import { UserDB } from "./UserDB"; import { RequestHandler } from "./RequestHandler"; import { EmailService as EmailService } from "./email/EmailService"; import { TokenDB } from "./TokenDB"; import { UserManagementServerSettings } from "./UserManagementServerSettings"; import { RJLog } from "../log/RJLog"; import { SerializedSession, Session } from "./Session"; import { Role } from "./permissions/Role"; import { RolesData } from "./permissions/RolesData"; import { Files } from "../files/Files"; import { LocationService } from "./location/LocationService"; import { RequestRequirement } from "./requirements/RequestRequirement"; import { NotTooManyRequests } from "./requirements/security/NotTooManyRequests"; import { FilesSync } from "../files/FilesSync"; import { UserData } from "./UserData"; import { Scheduler } from "./scheduler/Scheduler"; import { DateHelper } from "../../browser/date/DateHelper"; import { DateFormatter } from "../../browser/date/DateFormatter"; import { Duration } from "../../browser/date/Duration"; import { Task } from "./scheduler/Task"; import { UserApp } from "./apps/UserApp"; import { UserAppFactory } from "./apps/UserAppFactory"; import { iTaskScheduler } from "./scheduler/iTaskScheduler"; import { Token } from "./Token"; export class UserManagementServer { _app:FastifyInstance; get app(){ return this._app; } _userDB:UserDB; get userDB(){ return this._userDB; } _tokenDB:TokenDB; get tokenDB(){ return this._tokenDB;} _emailService:EmailService; get email(){ return this._emailService; } _locationService:LocationService; get location(){ return this._locationService; } _sessions:Map = new Map(); _roles = new Map(); getSessionData():SerializedSession[] { let sessionData:SerializedSession[] = []; for ( let idsession of this._sessions ) { let session = idsession[ 1 ]; let token = this.tokenDB._tokens.get( session.token ); let userID = session.userID; let serializedSession = SerializedSession.create( token, userID ); sessionData.push( serializedSession ); } return sessionData; } async saveSessionData():Promise { if ( ! this._settings.persistSessions ) { return Promise.resolve(); } let sessionData = this.getSessionData(); await Files.saveJSON( this._settings.sessionsPath, sessionData ); return Promise.resolve(); } async loadSessionData():Promise { if ( ! this._settings.persistSessions ) { return Promise.resolve(); } let sessionsExist = await Files.exists( this._settings.sessionsPath ); if ( ! sessionsExist ) { return; } let sessionData = await Files.loadJSON( this._settings.sessionsPath ); sessionData.forEach( ( sd )=> { let token = new Token(); token.id = sd.id; token.hashedIP = sd.hashedIP; token.expires = sd.expires; this._tokenDB._tokens.set( token.id, token ); let session = new Session(); session.userID = sd.userID; session.token = sd.id; this._sessions.set( session.token, session ); } ); } _handlers:RequestHandler[] = []; _settings:UserManagementServerSettings; _globalRequirements:RequestRequirement[] = []; get globalRequirements(){ return this._globalRequirements; } _apps:UserApp[] = []; get apps(){ return this._apps; } _scheduler:Scheduler; get scheduler(){ return this._scheduler; } readonly onFileChanged = new EventSlot(); async initialize( settings:UserManagementServerSettings, mailService:EmailService, handlers:RequestHandler[], globalRequirements:RequestRequirement[] = null ):Promise { this._settings = settings; UserManagementServerSettings.makeAllPathsAbsolute( settings ); await this._initializeApp(); await this._addGlobalRequirements( globalRequirements || UserManagementServer.DefaultGlobalRequirements() ); await this._addServices( mailService ); await this._addHandlers( handlers ); await this._startApps(); await this._startServer(); this._update(); return Promise.resolve(); } static DefaultGlobalRequirements():RequestRequirement[] { let globalDefaults:RequestRequirement[] = [ new NotTooManyRequests() ]; return globalDefaults; } async _addGlobalRequirements( globalRequirements:RequestRequirement[] ):Promise { this._globalRequirements = globalRequirements; for ( let gr of this._globalRequirements ) { await gr.initializeGlobal( this ); } return Promise.resolve(); } getUser( request:FastifyRequest ):Promise { let requestBody = request.body; let tokenData = requestBody as { token:string }; let tokenID = tokenData.token; let session = this._sessions.get( tokenID ); return this.userDB.byID( session.userID ); } async sendEmail( to:string, title:string, message:string ):Promise { return this.email.send( this._settings.emailFrom, to, title, message ); } async _initializeApp():Promise { if ( this._settings.isDebugMode ) { this._app = Fastify(); } else { let httpsKey = FilesSync.loadUTF8( this._settings.httpsKeyPath ); let httpsCert = FilesSync.loadUTF8( this._settings.httpsCertPath ); RJLog.log( "Key", httpsKey ); RJLog.log( "Cert", httpsCert ); this._app = Fastify( { https: { key:httpsKey, cert:httpsCert } } ); } this._app.register( fastifyMultipart ); if ( this._settings.isDebugMode ) { RJLog.log( "Setting any cors:" ); await this._app.register( cors, { origin: "*", }); } else { for ( let corsURL of this._settings.corsURLs ) { RJLog.log( "Adding cors:", corsURL ); await this._app.register( cors, { origin: corsURL, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] } ); } } return Promise.resolve(); } _updateDuration = 60; protected _update() { let self = this; let waitTimeMS = Duration.toMilliSeconds( this._updateDuration ); setTimeout( () =>{ self._update() }, waitTimeMS ); } async _addServices( mailService:EmailService ):Promise { this._userDB = await UserDB.load( this, this._settings.userDBPath ); this._scheduler = new Scheduler(); // RJLog.log( "Loading roles:", this._settings.rolesPath ); let rolesData = await Files.loadJSON( this._settings.rolesPath ); this._roles = new Map(); rolesData.roles.forEach( r => this._roles.set( r.id, r ) ); this._tokenDB = new TokenDB( this ); this._emailService = mailService; this._locationService = await LocationService.create( this._settings.geoLocationPath, this._settings.geoAccountID, this._settings.geoLicenseKey ); let ipToCheck = "2a02:3100:25e5:2500:65d7:61b7:33f7:9d7f"; let location = await this._locationService.getLocation( ipToCheck ); RJLog.log( "IP", ipToCheck, "Location:", location ); return Promise.resolve(); } async _addHandlers( handlers:RequestHandler[] ):Promise { for ( let handler of handlers ) { await handler.initialize( this ); this._handlers.push( handler ); } return Promise.resolve(); } async _startServer() { let listenOptions:FastifyListenOptions = { port: this._settings.port } // if ( ! this._settings.isDebugMode ) // { listenOptions.host = "0.0.0.0"; // } this.app.listen( listenOptions, ( error, address ) => { if ( error ) { RJLog.error( error ); throw error; } RJLog.log( `Server running on ${address}` ); } ); } async _startApps() { for ( let appID of this._settings.userApps ) { let app = UserAppFactory.create( appID, this ); RJLog.log( "Starting app:", app.id ); this._apps.push( app ); } for ( let app of this._apps ) { await app.initialize(); if ( app.schedulesTasks ) { this.scheduler._taskSchedulers.push( app as any as iTaskScheduler ); } } await this._scheduler.update(); await this.loadSessionData(); } }