2024-05-28 16:45:34 +00:00
import * as plugins from '../plugins.js' ;
2024-05-30 20:49:39 +00:00
import type { Cloudly } from '../classes.cloudly.js' ;
import { logger } from '../logger.js' ;
2024-05-28 16:45:34 +00:00
import { Authorization } from './classes.authorization.js' ;
import { User } from './classes.user.js' ;
2024-05-30 20:49:39 +00:00
export interface IJwtData {
userId : string ;
status : 'loggedIn' | 'loggedOut' ;
2024-08-25 12:29:26 +00:00
expiresAt : number ;
2024-05-30 20:49:39 +00:00
}
export class CloudlyAuthManager {
2024-05-28 16:45:34 +00:00
cloudlyRef : Cloudly
public get db() {
return this . cloudlyRef . mongodbConnector . smartdataDb ;
}
public CUser = plugins . smartdata . setDefaultManagerForDoc ( this , User ) ;
public CAuthorization = plugins . smartdata . setDefaultManagerForDoc ( this , Authorization ) ;
2024-05-30 20:49:39 +00:00
public typedrouter = new plugins . typedrequest . TypedRouter ( ) ;
public smartjwtInstance : plugins.smartjwt.SmartJwt < IJwtData > ;
2024-05-28 16:45:34 +00:00
constructor ( cloudlyRef : Cloudly ) {
this . cloudlyRef = cloudlyRef ;
2024-05-30 20:49:39 +00:00
this . cloudlyRef . typedrouter . addTypedRouter ( this . typedrouter ) ;
2024-05-28 16:45:34 +00:00
}
2024-05-30 20:49:39 +00:00
2024-10-16 12:35:38 +00:00
public async createNewSecureToken() {
return plugins . smartunique . uniSimple ( 'secureToken' , 64 ) ;
}
2024-05-30 20:49:39 +00:00
public async start() {
// lets setup the smartjwtInstance
this . smartjwtInstance = new plugins . smartjwt . SmartJwt ( ) ;
2024-06-01 03:48:57 +00:00
await this . smartjwtInstance . init ( ) ;
2024-05-30 20:49:39 +00:00
const kvStore = await this . cloudlyRef . config . appData . getKvStore ( ) ;
2024-08-25 12:29:26 +00:00
const existingJwtKeys : plugins.tsclass.network.IJwtKeypair = ( await kvStore . readKey ( 'jwtKeypair' ) ) as plugins . tsclass . network . IJwtKeypair ;
2024-05-30 20:49:39 +00:00
if ( ! existingJwtKeys ) {
await this . smartjwtInstance . createNewKeyPair ( ) ;
const newJwtKeys = this . smartjwtInstance . getKeyPairAsJson ( ) ;
2024-08-25 12:29:26 +00:00
await kvStore . writeKey ( 'jwtKeypair' , newJwtKeys ) ;
2024-05-30 20:49:39 +00:00
} else {
this . smartjwtInstance . setKeyPairAsJson ( existingJwtKeys ) ;
}
this . typedrouter . addTypedHandler (
new plugins . typedrequest . TypedHandler < plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword > (
'adminLoginWithUsernameAndPassword' ,
async ( dataArg ) = > {
let jwt : string ;
2024-08-25 12:29:26 +00:00
let expiresAtTimestamp : number = Date . now ( ) + 3600 * 1000 * 24 * 7 ;
2024-05-30 20:49:39 +00:00
const user = await User . findUserByUsernameAndPassword ( dataArg . username , dataArg . password ) ;
if ( ! user ) {
logger . log ( 'warn' , 'login failed' ) ;
2024-10-16 12:35:38 +00:00
throw new plugins . typedrequest . TypedResponseError ( 'login failed' ) ;
2024-05-30 20:49:39 +00:00
} else {
2024-06-01 03:48:57 +00:00
jwt = await this . smartjwtInstance . createJWT ( {
2024-05-30 20:49:39 +00:00
userId : user.id ,
status : 'loggedIn' ,
2024-08-25 12:29:26 +00:00
expiresAt : expiresAtTimestamp ,
2024-05-30 20:49:39 +00:00
} ) ;
logger . log ( 'success' , 'login successful' ) ;
}
return {
2024-08-25 12:29:26 +00:00
identity : {
jwt ,
userId : user.id ,
name : user.data.username ,
expiresAt : expiresAtTimestamp ,
role : user.data.role ,
type : user . data . type ,
} ,
2024-05-30 20:49:39 +00:00
} ;
}
)
) ;
}
public async stop ( ) { }
2024-08-25 12:29:26 +00:00
public validIdentityGuard = new plugins . smartguard . Guard < { identity : plugins.servezoneInterfaces.data.IIdentity } > ( async ( dataArg ) = > {
const jwt = dataArg . identity . jwt ;
const jwtData : IJwtData = await this . smartjwtInstance . verifyJWTAndGetData ( jwt ) ;
const expired = jwtData . expiresAt < Date . now ( ) ;
plugins . smartexpect . expect ( jwtData . status ) . setFailMessage ( 'user not logged in' ) . toEqual ( 'loggedIn' ) ;
plugins . smartexpect . expect ( expired ) . setFailMessage ( ` jwt expired ` ) . toBeFalse ( ) ;
plugins . smartexpect . expect ( dataArg . identity . expiresAt ) . setFailMessage ( ` expiresAt >>identity valid until: ${ dataArg . identity . expiresAt } , but jwt says: ${ jwtData . expiresAt } << has been tampered with ` ) . toEqual ( jwtData . expiresAt ) ;
plugins . smartexpect . expect ( dataArg . identity . userId ) . setFailMessage ( 'userId has been tampered with' ) . toEqual ( jwtData . userId ) ;
if ( expired ) {
throw new Error ( 'identity is expired' ) ;
}
return true ;
} , {
failedHint : 'identity is not valid.' ,
name : 'validIdentityGuard' ,
} ) ;
public adminIdentityGuard = new plugins . smartguard . Guard < { identity : plugins.servezoneInterfaces.data.IIdentity } > ( async ( dataArg ) = > {
2024-10-16 12:35:38 +00:00
await plugins . smartguard . passGuardsOrReject ( dataArg , [ this . validIdentityGuard ] ) ;
2024-08-25 12:29:26 +00:00
const jwt = dataArg . identity . jwt ;
2024-06-01 03:48:57 +00:00
const jwtData : IJwtData = await this . smartjwtInstance . verifyJWTAndGetData ( jwt ) ;
2024-05-30 20:49:39 +00:00
const user = await this . CUser . getInstance ( { id : jwtData.userId } ) ;
2024-06-01 03:48:57 +00:00
const isAdminBool = user . data . role === 'admin' ;
console . log ( ` user is admin: ${ isAdminBool } ` ) ;
return isAdminBool ;
} , {
2024-08-25 12:29:26 +00:00
failedHint : 'user is not admin.' ,
name : 'adminIdentityGuard' ,
2024-05-30 20:49:39 +00:00
} )
2024-05-28 16:45:34 +00:00
}