feat(gaurds): use better smartguards to verify action authorization

This commit is contained in:
Philipp Kunz 2024-05-30 22:49:39 +02:00
parent 6b3fd2ce31
commit 929f250006
18 changed files with 1116 additions and 145 deletions

View File

@ -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",

File diff suppressed because it is too large Load Diff

View File

@ -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.'
}

View File

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

View File

@ -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) {

View File

@ -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

View File

@ -0,0 +1,9 @@
export const users = [
{
id: 'user1',
data: {
username: 'admin',
password: 'password',
}
}
]

View File

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

View File

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

View File

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

View File

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

View File

@ -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';

View File

@ -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(

View File

@ -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>(

View File

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

View File

@ -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

View File

@ -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.'
}