import * as plugins from '../plugins.js'; import type { Cloudly } from '../classes.cloudly.js'; import { logger } from '../logger.js'; import { Authorization } from './classes.authorization.js'; import { User } from './classes.user.js'; export interface IJwtData { userId: string; status: 'loggedIn' | 'loggedOut'; expiresAt: number; } export class CloudlyAuthManager { cloudlyRef: Cloudly public get db() { return this.cloudlyRef.mongodbConnector.smartdataDb; } public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User); public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization); public typedrouter = new plugins.typedrequest.TypedRouter(); public smartjwtInstance: plugins.smartjwt.SmartJwt; constructor(cloudlyRef: Cloudly) { this.cloudlyRef = cloudlyRef; this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter); } public async start() { // lets setup the smartjwtInstance this.smartjwtInstance = new plugins.smartjwt.SmartJwt(); await this.smartjwtInstance.init(); const kvStore = await this.cloudlyRef.config.appData.getKvStore(); const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey('jwtKeypair')) as plugins.tsclass.network.IJwtKeypair; if (!existingJwtKeys) { await this.smartjwtInstance.createNewKeyPair(); const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson(); await kvStore.writeKey('jwtKeypair', newJwtKeys); } else { this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys); } this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'adminLoginWithUsernameAndPassword', async (dataArg) => { let jwt: string; let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7; const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password); if (!user) { logger.log('warn', 'login failed'); } else { jwt = await this.smartjwtInstance.createJWT({ userId: user.id, status: 'loggedIn', expiresAt: expiresAtTimestamp, }); logger.log('success', 'login successful'); } return { identity: { jwt, userId: user.id, name: user.data.username, expiresAt: expiresAtTimestamp, role: user.data.role, type: user.data.type, }, }; } ) ); } public async stop () {} 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) => { await plugins.smartexpect.expectAsync(this.validIdentityGuard.exec(dataArg)).toBeTrue(); const jwt = dataArg.identity.jwt; const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt); const user = await this.CUser.getInstance({id: jwtData.userId}); const isAdminBool = user.data.role === 'admin'; console.log(`user is admin: ${isAdminBool}`); return isAdminBool; }, { failedHint: 'user is not admin.', name: 'adminIdentityGuard', }) }