feat(gaurds): use better smartguards to verify action authorization
This commit is contained in:
		| @@ -26,10 +26,10 @@ | ||||
|     "@git.zone/tstest": "^1.0.90", | ||||
|     "@git.zone/tswatch": "^2.0.23", | ||||
|     "@push.rocks/tapbundle": "^5.0.23", | ||||
|     "@types/node": "^20.12.12" | ||||
|     "@types/node": "^20.12.13" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@api.global/typedrequest": "3.0.25", | ||||
|     "@api.global/typedrequest": "3.0.28", | ||||
|     "@api.global/typedserver": "^3.0.50", | ||||
|     "@api.global/typedsocket": "^3.0.1", | ||||
|     "@apiclient.xyz/cloudflare": "^6.0.1", | ||||
| @@ -51,7 +51,7 @@ | ||||
|     "@push.rocks/smartdelay": "^3.0.5", | ||||
|     "@push.rocks/smartexit": "^1.0.23", | ||||
|     "@push.rocks/smartfile": "^11.0.15", | ||||
|     "@push.rocks/smartguard": "^2.0.1", | ||||
|     "@push.rocks/smartguard": "^3.0.2", | ||||
|     "@push.rocks/smartjson": "^5.0.19", | ||||
|     "@push.rocks/smartjwt": "^2.0.4", | ||||
|     "@push.rocks/smartlog": "^3.0.6", | ||||
|   | ||||
							
								
								
									
										1087
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1087
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@serve.zone/cloudly', | ||||
|   version: '1.0.216', | ||||
|   version: '1.1.0', | ||||
|   description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.' | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,8 @@ import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js' | ||||
| import { CloudlyServerManager } from './manager.server/servermanager.js'; | ||||
| import { ExternalApiManager } from './manager.status/statusmanager.js'; | ||||
| import { ImageManager } from './manager.image/classes.imagemanager.js'; | ||||
| import { logger } from './cloudly.logging.js'; | ||||
| import { logger } from './logger.js'; | ||||
| import { CloudlyAuthManager } from './manager.auth/classes.authmanager.js'; | ||||
|  | ||||
| /** | ||||
|  * Cloudly class can be used to instantiate a cloudly server. | ||||
| @@ -48,6 +49,7 @@ export class Cloudly { | ||||
|   public mongodbConnector: MongodbConnector; | ||||
|  | ||||
|   // managers | ||||
|   public authManager: CloudlyAuthManager; | ||||
|   public secretManager: CloudlySecretManager; | ||||
|   public clusterManager: ClusterManager; | ||||
|   public coreflowManager: CloudlyCoreflowManager; | ||||
| @@ -71,7 +73,8 @@ export class Cloudly { | ||||
|     this.cloudflareConnector = new CloudflareConnector(this); | ||||
|     this.letsencryptConnector = new LetsencryptConnector(this); | ||||
|  | ||||
|     // processes | ||||
|     // managers | ||||
|     this.authManager = new CloudlyAuthManager(this); | ||||
|     this.clusterManager = new ClusterManager(this); | ||||
|     this.coreflowManager = new CloudlyCoreflowManager(this); | ||||
|     this.externalApiManager = new ExternalApiManager(this); | ||||
| @@ -90,6 +93,7 @@ export class Cloudly { | ||||
|     await this.config.init(); | ||||
|  | ||||
|     // manageers | ||||
|     await this.authManager.start(); | ||||
|     await this.secretManager.start(); | ||||
|     await this.serverManager.start(); | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as paths from './paths.js'; | ||||
| import { logger } from './cloudly.logging.js'; | ||||
| import { logger } from './logger.js'; | ||||
| import type { Cloudly } from './classes.cloudly.js'; | ||||
|  | ||||
| /** | ||||
| @@ -56,19 +56,8 @@ export class CloudlyConfig { | ||||
|       ], | ||||
|     }); | ||||
|  | ||||
|     this.smartjwtInstance = new plugins.smartjwt.SmartJwt(); | ||||
|     const kvStore = await this.appData.getKvStore(); | ||||
|  | ||||
|     const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys'); | ||||
|  | ||||
|     if (!existingJwtKeys) { | ||||
|       await this.smartjwtInstance.createNewKeyPair(); | ||||
|       const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson(); | ||||
|       await kvStore.writeKey('jwtKeys', newJwtKeys); | ||||
|     } else { | ||||
|       this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys); | ||||
|     } | ||||
|  | ||||
|     this.data = await kvStore.readAll(); | ||||
|     const missingKeys = await this.appData.logMissingKeys(); | ||||
|     if (missingKeys.length > 0) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as paths from './paths.js'; | ||||
| import { Cloudly } from './classes.cloudly.js'; | ||||
| import { logger } from './cloudly.logging.js'; | ||||
| import { logger } from './logger.js'; | ||||
|  | ||||
| /** | ||||
|  * handles incoming requests from CI to deploy new versions of apps | ||||
|   | ||||
							
								
								
									
										9
									
								
								ts/demo/demo.data.users.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ts/demo/demo.data.users.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| export const users = [ | ||||
|   { | ||||
|     id: 'user1', | ||||
|     data: { | ||||
|       username: 'admin', | ||||
|       password: 'password', | ||||
|     } | ||||
|   } | ||||
| ] | ||||
| @@ -35,4 +35,17 @@ export const installDemoData = async (cloudlyRef: Cloudly) => { | ||||
|     await cluster.delete(); | ||||
|   } | ||||
|  | ||||
|   // ================================================================================ | ||||
|   // USERS | ||||
|   const users = await cloudlyRef.authManager.CUser.getInstances({}); | ||||
|   for (const user of users) { | ||||
|     await user.delete(); | ||||
|   } | ||||
|  | ||||
|   const demoDataUsers = await import('./demo.data.users.js'); | ||||
|   for (const user of demoDataUsers.users) { | ||||
|     const userInstance = new cloudlyRef.authManager.CUser(); | ||||
|     Object.assign(userInstance, user); | ||||
|     await userInstance.save(); | ||||
|   } | ||||
| } | ||||
| @@ -3,7 +3,7 @@ early.start('cloudly'); | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as paths from './paths.js'; | ||||
| import { Cloudly } from './classes.cloudly.js'; | ||||
| import { logger } from './cloudly.logging.js'; | ||||
| import { logger } from './logger.js'; | ||||
| const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true); | ||||
| early.stop(); | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,17 @@ | ||||
| import type { Cloudly } from '../classes.cloudly.js'; | ||||
| 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 class AuthManager { | ||||
|  | ||||
| export interface IJwtData { | ||||
|   userId: string; | ||||
|   status: 'loggedIn' | 'loggedOut'; | ||||
| } | ||||
|  | ||||
| export class CloudlyAuthManager { | ||||
|   cloudlyRef: Cloudly | ||||
|   public get db() { | ||||
|     return this.cloudlyRef.mongodbConnector.smartdataDb; | ||||
| @@ -11,7 +19,58 @@ export class AuthManager { | ||||
|   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<IJwtData>; | ||||
|  | ||||
|   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(); | ||||
|     const kvStore = await this.cloudlyRef.config.appData.getKvStore(); | ||||
|  | ||||
|     const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys'); | ||||
|  | ||||
|     if (!existingJwtKeys) { | ||||
|       await this.smartjwtInstance.createNewKeyPair(); | ||||
|       const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson(); | ||||
|       await kvStore.writeKey('jwtKeys', newJwtKeys); | ||||
|     } 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; | ||||
|           const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password); | ||||
|           if (!user) { | ||||
|             logger.log('warn', 'login failed'); | ||||
|           } else { | ||||
|             jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({ | ||||
|               userId: user.id, | ||||
|               status: 'loggedIn', | ||||
|             }); | ||||
|             logger.log('success', 'login successful'); | ||||
|           } | ||||
|           return { | ||||
|             jwt, | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   public async stop () {} | ||||
|  | ||||
|   public adminJwtGuard = new plugins.smartguard.Guard<{jwt: string}>(async (dataArg) => { | ||||
|     const jwt = dataArg.jwt; | ||||
|     const jwtData: IJwtData = await this.cloudlyRef.config.smartjwtInstance.verifyJWTAndGetData(jwt); | ||||
|     const user = await this.CUser.getInstance({id: jwtData.userId}); | ||||
|     return user.data.role === 'admin'; | ||||
|   }) | ||||
| } | ||||
| @@ -2,5 +2,23 @@ import * as plugins from '../plugins.js'; | ||||
|  | ||||
| @plugins.smartdata.managed() | ||||
| export class User extends plugins.smartdata.SmartDataDbDoc<User, User> { | ||||
|    | ||||
|   public static async findUserByUsernameAndPassword(usernameArg: string, passwordArg: string) { | ||||
|     return await User.getInstance({ | ||||
|       data: { | ||||
|         username: usernameArg, | ||||
|         password: passwordArg, | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // INSTANCE | ||||
|   @plugins.smartdata.unI() | ||||
|   public id: string; | ||||
|  | ||||
|   @plugins.smartdata.svDb() | ||||
|   public data: { | ||||
|     role: 'admin' | 'user'; | ||||
|     username: string; | ||||
|     password: string; | ||||
|   } | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import * as paths from '../paths.js'; | ||||
| import { Cloudly } from '../classes.cloudly.js'; | ||||
| import { logger } from '../cloudly.logging.js'; | ||||
| import { logger } from '../logger.js'; | ||||
|  | ||||
| import { Cluster } from './cluster.js'; | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,8 @@ export class ImageManager { | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>( | ||||
|         'getAllImages', | ||||
|         async (requestArg) => { | ||||
|         async (requestArg, toolsArg) => { | ||||
|           await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], requestArg); | ||||
|           const images = await this.CImage.getInstances({}); | ||||
|           return { | ||||
|             images: await Promise.all( | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import * as plugins from '../plugins.js'; | ||||
| import * as paths from '../paths.js'; | ||||
| import { SecretBundle } from './classes.secretbundle.js'; | ||||
| import { SecretGroup } from './classes.secretgroup.js'; | ||||
| import { logger } from '../cloudly.logging.js'; | ||||
| import { logger } from '../logger.js'; | ||||
| import type { Cloudly } from '../classes.cloudly.js'; | ||||
|  | ||||
| /** | ||||
| @@ -34,27 +34,6 @@ export class CloudlySecretManager { | ||||
|     // lets set up a typedrouter | ||||
|     this.typedrouter = new plugins.typedrequest.TypedRouter(); | ||||
|     this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter); | ||||
|      | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>( | ||||
|         'adminLoginWithUsernameAndPassword', | ||||
|         async (dataArg) => { | ||||
|           let jwt: string; | ||||
|           // console.log(dataArg); | ||||
|           if (dataArg.username !== 'admin' || dataArg.password !== 'password') { | ||||
|             logger.log('warn', 'login failed'); | ||||
|           } else { | ||||
|             jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({ | ||||
|               status: 'loggedIn', | ||||
|             }); | ||||
|             logger.log('success', 'login successful'); | ||||
|           } | ||||
|           return { | ||||
|             jwt, | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>( | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { Cloudly } from '../classes.cloudly.js'; | ||||
|  | ||||
| import { logger } from '../cloudly.logging.js'; | ||||
| import { logger } from '../logger.js'; | ||||
|  | ||||
| export class CloudlyTaskmanager { | ||||
|   public cloudlyRef: Cloudly; | ||||
|   | ||||
| @@ -32,7 +32,6 @@ import * as smartcli from '@push.rocks/smartcli'; | ||||
| import * as smartdata from '@push.rocks/smartdata'; | ||||
| import * as smartdelay from '@push.rocks/smartdelay'; | ||||
| import * as smartexit from '@push.rocks/smartexit'; | ||||
| import * as typedserver from '@api.global/typedserver'; | ||||
| import * as smartfile from '@push.rocks/smartfile'; | ||||
| import * as smartguard from '@push.rocks/smartguard'; | ||||
| import * as smartjson from '@push.rocks/smartjson'; | ||||
| @@ -45,6 +44,7 @@ import * as smartssh from '@push.rocks/smartssh'; | ||||
| import * as smartstring from '@push.rocks/smartstring'; | ||||
| import * as smartunique from '@push.rocks/smartunique'; | ||||
| import * as taskbuffer from '@push.rocks/taskbuffer'; | ||||
| import * as typedserver from '@api.global/typedserver'; | ||||
|  | ||||
| export { | ||||
|   npmextra, | ||||
| @@ -55,7 +55,6 @@ export { | ||||
|   smartcli, | ||||
|   smartdata, | ||||
|   smartexit, | ||||
|   typedserver, | ||||
|   smartdelay, | ||||
|   smartfile, | ||||
|   smartguard, | ||||
| @@ -69,6 +68,7 @@ export { | ||||
|   smartstring, | ||||
|   smartunique, | ||||
|   taskbuffer, | ||||
|   typedserver, | ||||
| }; | ||||
|  | ||||
| // @servezone scope | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@serve.zone/cloudly', | ||||
|   version: '1.0.216', | ||||
|   version: '1.1.0', | ||||
|   description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.' | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user