feat(gaurds): use better smartguards to verify action authorization
This commit is contained in:
parent
6b3fd2ce31
commit
929f250006
@ -26,10 +26,10 @@
|
|||||||
"@git.zone/tstest": "^1.0.90",
|
"@git.zone/tstest": "^1.0.90",
|
||||||
"@git.zone/tswatch": "^2.0.23",
|
"@git.zone/tswatch": "^2.0.23",
|
||||||
"@push.rocks/tapbundle": "^5.0.23",
|
"@push.rocks/tapbundle": "^5.0.23",
|
||||||
"@types/node": "^20.12.12"
|
"@types/node": "^20.12.13"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest": "3.0.25",
|
"@api.global/typedrequest": "3.0.28",
|
||||||
"@api.global/typedserver": "^3.0.50",
|
"@api.global/typedserver": "^3.0.50",
|
||||||
"@api.global/typedsocket": "^3.0.1",
|
"@api.global/typedsocket": "^3.0.1",
|
||||||
"@apiclient.xyz/cloudflare": "^6.0.1",
|
"@apiclient.xyz/cloudflare": "^6.0.1",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartexit": "^1.0.23",
|
"@push.rocks/smartexit": "^1.0.23",
|
||||||
"@push.rocks/smartfile": "^11.0.15",
|
"@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/smartjson": "^5.0.19",
|
||||||
"@push.rocks/smartjwt": "^2.0.4",
|
"@push.rocks/smartjwt": "^2.0.4",
|
||||||
"@push.rocks/smartlog": "^3.0.6",
|
"@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 = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
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.'
|
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 { CloudlyServerManager } from './manager.server/servermanager.js';
|
||||||
import { ExternalApiManager } from './manager.status/statusmanager.js';
|
import { ExternalApiManager } from './manager.status/statusmanager.js';
|
||||||
import { ImageManager } from './manager.image/classes.imagemanager.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.
|
* Cloudly class can be used to instantiate a cloudly server.
|
||||||
@ -48,6 +49,7 @@ export class Cloudly {
|
|||||||
public mongodbConnector: MongodbConnector;
|
public mongodbConnector: MongodbConnector;
|
||||||
|
|
||||||
// managers
|
// managers
|
||||||
|
public authManager: CloudlyAuthManager;
|
||||||
public secretManager: CloudlySecretManager;
|
public secretManager: CloudlySecretManager;
|
||||||
public clusterManager: ClusterManager;
|
public clusterManager: ClusterManager;
|
||||||
public coreflowManager: CloudlyCoreflowManager;
|
public coreflowManager: CloudlyCoreflowManager;
|
||||||
@ -71,7 +73,8 @@ export class Cloudly {
|
|||||||
this.cloudflareConnector = new CloudflareConnector(this);
|
this.cloudflareConnector = new CloudflareConnector(this);
|
||||||
this.letsencryptConnector = new LetsencryptConnector(this);
|
this.letsencryptConnector = new LetsencryptConnector(this);
|
||||||
|
|
||||||
// processes
|
// managers
|
||||||
|
this.authManager = new CloudlyAuthManager(this);
|
||||||
this.clusterManager = new ClusterManager(this);
|
this.clusterManager = new ClusterManager(this);
|
||||||
this.coreflowManager = new CloudlyCoreflowManager(this);
|
this.coreflowManager = new CloudlyCoreflowManager(this);
|
||||||
this.externalApiManager = new ExternalApiManager(this);
|
this.externalApiManager = new ExternalApiManager(this);
|
||||||
@ -90,6 +93,7 @@ export class Cloudly {
|
|||||||
await this.config.init();
|
await this.config.init();
|
||||||
|
|
||||||
// manageers
|
// manageers
|
||||||
|
await this.authManager.start();
|
||||||
await this.secretManager.start();
|
await this.secretManager.start();
|
||||||
await this.serverManager.start();
|
await this.serverManager.start();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './paths.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';
|
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 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();
|
this.data = await kvStore.readAll();
|
||||||
const missingKeys = await this.appData.logMissingKeys();
|
const missingKeys = await this.appData.logMissingKeys();
|
||||||
if (missingKeys.length > 0) {
|
if (missingKeys.length > 0) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { Cloudly } from './classes.cloudly.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
|
* 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();
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { Cloudly } from './classes.cloudly.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);
|
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
|
||||||
early.stop();
|
early.stop();
|
||||||
|
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import type { Cloudly } from '../classes.cloudly.js';
|
|
||||||
import * as plugins from '../plugins.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 { Authorization } from './classes.authorization.js';
|
||||||
import { User } from './classes.user.js';
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
export class AuthManager {
|
|
||||||
|
export interface IJwtData {
|
||||||
|
userId: string;
|
||||||
|
status: 'loggedIn' | 'loggedOut';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloudlyAuthManager {
|
||||||
cloudlyRef: Cloudly
|
cloudlyRef: Cloudly
|
||||||
public get db() {
|
public get db() {
|
||||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||||
@ -11,7 +19,58 @@ export class AuthManager {
|
|||||||
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
|
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
|
||||||
public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);
|
public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);
|
||||||
|
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
|
||||||
|
|
||||||
constructor(cloudlyRef: Cloudly) {
|
constructor(cloudlyRef: Cloudly) {
|
||||||
this.cloudlyRef = cloudlyRef;
|
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()
|
@plugins.smartdata.managed()
|
||||||
export class User extends plugins.smartdata.SmartDataDbDoc<User, User> {
|
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 plugins from '../plugins.js';
|
||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { Cloudly } from '../classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
import { logger } from '../cloudly.logging.js';
|
import { logger } from '../logger.js';
|
||||||
|
|
||||||
import { Cluster } from './cluster.js';
|
import { Cluster } from './cluster.js';
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ export class ImageManager {
|
|||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||||
'getAllImages',
|
'getAllImages',
|
||||||
async (requestArg) => {
|
async (requestArg, toolsArg) => {
|
||||||
|
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], requestArg);
|
||||||
const images = await this.CImage.getInstances({});
|
const images = await this.CImage.getInstances({});
|
||||||
return {
|
return {
|
||||||
images: await Promise.all(
|
images: await Promise.all(
|
||||||
|
@ -2,7 +2,7 @@ import * as plugins from '../plugins.js';
|
|||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { SecretBundle } from './classes.secretbundle.js';
|
import { SecretBundle } from './classes.secretbundle.js';
|
||||||
import { SecretGroup } from './classes.secretgroup.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';
|
import type { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,27 +34,6 @@ export class CloudlySecretManager {
|
|||||||
// lets set up a typedrouter
|
// lets set up a typedrouter
|
||||||
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
this.cloudlyRef.typedrouter.addTypedRouter(this.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(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
import { logger } from '../cloudly.logging.js';
|
import { logger } from '../logger.js';
|
||||||
|
|
||||||
export class CloudlyTaskmanager {
|
export class CloudlyTaskmanager {
|
||||||
public cloudlyRef: Cloudly;
|
public cloudlyRef: Cloudly;
|
||||||
|
@ -32,7 +32,6 @@ import * as smartcli from '@push.rocks/smartcli';
|
|||||||
import * as smartdata from '@push.rocks/smartdata';
|
import * as smartdata from '@push.rocks/smartdata';
|
||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
import * as smartexit from '@push.rocks/smartexit';
|
import * as smartexit from '@push.rocks/smartexit';
|
||||||
import * as typedserver from '@api.global/typedserver';
|
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
import * as smartguard from '@push.rocks/smartguard';
|
import * as smartguard from '@push.rocks/smartguard';
|
||||||
import * as smartjson from '@push.rocks/smartjson';
|
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 smartstring from '@push.rocks/smartstring';
|
||||||
import * as smartunique from '@push.rocks/smartunique';
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||||
|
import * as typedserver from '@api.global/typedserver';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
npmextra,
|
npmextra,
|
||||||
@ -55,7 +55,6 @@ export {
|
|||||||
smartcli,
|
smartcli,
|
||||||
smartdata,
|
smartdata,
|
||||||
smartexit,
|
smartexit,
|
||||||
typedserver,
|
|
||||||
smartdelay,
|
smartdelay,
|
||||||
smartfile,
|
smartfile,
|
||||||
smartguard,
|
smartguard,
|
||||||
@ -69,6 +68,7 @@ export {
|
|||||||
smartstring,
|
smartstring,
|
||||||
smartunique,
|
smartunique,
|
||||||
taskbuffer,
|
taskbuffer,
|
||||||
|
typedserver,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @servezone scope
|
// @servezone scope
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user