342 lines
8.4 KiB
TypeScript
342 lines
8.4 KiB
TypeScript
// src/web-cli.ts
|
|
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
|
|
// All Rights Reserved
|
|
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { Types } from "mongoose";
|
|
|
|
import "./lib/db.js";
|
|
|
|
import ApiClient, { ApiClientStatus } from "./models/api-client.js";
|
|
import User from "./models/user.js";
|
|
|
|
import ApiClientService from "./services/api-client.js";
|
|
import CryptoService from "./services/crypto.js";
|
|
import UserService from "./services/user.js";
|
|
|
|
import { DtpProcess } from "./lib/process.js";
|
|
|
|
class DtpWebCli extends DtpProcess {
|
|
get name(): string {
|
|
return "DtpWebCli";
|
|
}
|
|
get slug(): string {
|
|
return "dtp-web-cli";
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
async run(argv: string[]): Promise<void> {
|
|
const cmd = argv.shift();
|
|
if (!cmd) {
|
|
throw new Error("must specify command");
|
|
}
|
|
switch (cmd) {
|
|
case "admin":
|
|
return this.onAdminCmd(argv);
|
|
|
|
case "api-client":
|
|
return this.onApiClientCmd(argv);
|
|
|
|
case "user":
|
|
return this.onUserCmd(argv);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
throw new Error(`unknown command: ${cmd}`);
|
|
}
|
|
|
|
async onAdminCmd(argv: string[]): Promise<void> {
|
|
const action = argv.shift();
|
|
if (!action) {
|
|
throw new Error("must specify user command action");
|
|
}
|
|
switch (action) {
|
|
case "grant":
|
|
return this.onAdminGrant(argv);
|
|
case "revoke":
|
|
return this.onAdminRevoke(argv);
|
|
default:
|
|
break;
|
|
}
|
|
throw new Error(`unknown user command action: ${action}`);
|
|
}
|
|
|
|
async onAdminGrant(argv: string[]): Promise<void> {
|
|
const email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email for admin grant");
|
|
}
|
|
const user = await User.findOne({
|
|
email_lc: email.trim().toLowerCase(),
|
|
});
|
|
if (!user) {
|
|
throw new Error(`user ${email} not found`);
|
|
}
|
|
|
|
user.flags.isAdmin = true;
|
|
await user.save();
|
|
|
|
this.log.info(`admin rights granted to ${email}`);
|
|
}
|
|
|
|
async onAdminRevoke(argv: string[]): Promise<void> {
|
|
const email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email for admin revoke");
|
|
}
|
|
|
|
const user = await User.findOne({
|
|
email_lc: email.trim().toLowerCase(),
|
|
});
|
|
if (!user) {
|
|
throw new Error(`user ${email} not found`);
|
|
}
|
|
|
|
user.flags.isAdmin = false;
|
|
await user.save();
|
|
|
|
this.log.info(`admin rights revoked from ${email}`);
|
|
}
|
|
|
|
async onApiClientCmd(argv: string[]): Promise<void> {
|
|
const action = argv.shift();
|
|
if (!action) {
|
|
throw new Error("must specify api client command action");
|
|
}
|
|
switch (action) {
|
|
case "add":
|
|
return this.onApiClientAdd(argv);
|
|
|
|
case "ls":
|
|
return this.onApiClientList(argv);
|
|
|
|
case "status":
|
|
return this.onApiClientSetStatus(argv);
|
|
|
|
case "remove":
|
|
return this.onApiClientRemove(argv);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
throw new Error(`unknown api client command action: ${action}`);
|
|
}
|
|
|
|
async onApiClientAdd(argv: string[]): Promise<void> {
|
|
const name = argv.shift();
|
|
const description = argv.shift();
|
|
|
|
const client = await ApiClientService.create({
|
|
name,
|
|
description,
|
|
});
|
|
this.log.info("api client added", {
|
|
client: {
|
|
_id: client._id,
|
|
secret: client.secret,
|
|
name: client.name,
|
|
},
|
|
});
|
|
}
|
|
|
|
async onApiClientList(_argv: string[]): Promise<void> {
|
|
const clients = await ApiClient.find({ status: ApiClientStatus.Active })
|
|
.sort({ name: 1 })
|
|
.lean();
|
|
console.log("Name".padEnd(20), "Client ID".padEnd(24), "Secret");
|
|
console.log(
|
|
"--------------------------------------------------------------------------------"
|
|
);
|
|
for (const client of clients) {
|
|
console.log(client.name.padEnd(20), client._id.toString(), client.secret);
|
|
}
|
|
}
|
|
|
|
async onApiClientSetStatus(argv: string[]): Promise<void> {
|
|
const clientId = argv.shift();
|
|
if (!clientId) {
|
|
throw new Error("client ID is required");
|
|
}
|
|
const clientIdObj = Types.ObjectId.createFromHexString(clientId);
|
|
const client = await ApiClientService.getById(clientIdObj);
|
|
if (!client) {
|
|
throw new Error("Client not found");
|
|
}
|
|
|
|
const status = argv.shift() as ApiClientStatus;
|
|
if (!status) {
|
|
throw new Error("New client status is required");
|
|
}
|
|
|
|
await ApiClientService.setStatus(client, status);
|
|
this.log.info("API client status updated", { _id: clientId, status });
|
|
}
|
|
|
|
async onApiClientRemove(argv: string[]): Promise<void> {
|
|
const clientId = argv.shift();
|
|
if (!clientId) {
|
|
throw new Error("client ID is required");
|
|
}
|
|
const clientIdObj = Types.ObjectId.createFromHexString(clientId);
|
|
const client = await ApiClientService.getById(clientIdObj);
|
|
if (!client) {
|
|
throw new Error("Client not found");
|
|
}
|
|
await ApiClientService.remove(client);
|
|
this.log.info("API client removed", { _id: clientId });
|
|
}
|
|
|
|
async onUserCmd(argv: string[]): Promise<void> {
|
|
const action = argv.shift();
|
|
if (!action) {
|
|
throw new Error("must specify user command action");
|
|
}
|
|
switch (action) {
|
|
case "add":
|
|
return this.onUserAdd(argv);
|
|
case "password":
|
|
return this.onUserPassword(argv);
|
|
case "remove":
|
|
return this.onUserRemove(argv);
|
|
case "ban":
|
|
return this.onUserBan(argv);
|
|
default:
|
|
break;
|
|
}
|
|
throw new Error(`unknown user command action: ${action}`);
|
|
}
|
|
|
|
async onUserAdd(argv: string[]): Promise<void> {
|
|
const email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email address");
|
|
}
|
|
|
|
const password = argv.shift();
|
|
if (!password) {
|
|
throw new Error("must specify password");
|
|
}
|
|
|
|
const displayName: string | undefined = argv.shift();
|
|
|
|
const user = await UserService.create(email, password, displayName);
|
|
|
|
this.log.info(
|
|
`user created: id:${user._id.toHexString()}, email:${user.email}`
|
|
);
|
|
}
|
|
|
|
async onUserRemove(argv: string[]): Promise<void> {
|
|
let email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email address");
|
|
}
|
|
|
|
email = email.trim().toLowerCase();
|
|
const user = await User.findOne({ email }).lean();
|
|
if (!user) {
|
|
throw new Error(`user not found: ${email}`);
|
|
}
|
|
|
|
await User.deleteOne({ _id: user._id });
|
|
this.log.info(`user ${email} removed`);
|
|
}
|
|
|
|
async onUserBan(argv: string[]): Promise<void> {
|
|
const email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email address");
|
|
}
|
|
|
|
const email_lc = email.trim().toLowerCase();
|
|
const user = await User.findOne({ email_lc }).lean();
|
|
if (!user) {
|
|
throw new Error(`user not found: ${email}`);
|
|
}
|
|
|
|
await User.updateOne(
|
|
{ _id: user._id },
|
|
{
|
|
$set: {
|
|
"flags.isBanned": true,
|
|
},
|
|
}
|
|
);
|
|
this.log.info(`user ${email} banned`);
|
|
}
|
|
|
|
async onUserUnban(argv: string[]): Promise<void> {
|
|
const email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email address");
|
|
}
|
|
|
|
const email_lc = email.trim().toLowerCase();
|
|
const user = await User.findOne({ email_lc }).lean();
|
|
if (!user) {
|
|
throw new Error(`user not found: ${email}`);
|
|
}
|
|
|
|
await User.updateOne(
|
|
{ _id: user._id },
|
|
{
|
|
$set: {
|
|
"flags.isBanned": false,
|
|
},
|
|
}
|
|
);
|
|
this.log.info(`user ${email} unbanned`);
|
|
}
|
|
|
|
async onUserPassword(argv: string[]): Promise<void> {
|
|
let email = argv.shift();
|
|
if (!email) {
|
|
throw new Error("must specify email address");
|
|
}
|
|
|
|
const password = argv.shift();
|
|
if (!password) {
|
|
throw new Error("must specify password");
|
|
}
|
|
|
|
const email_lc = email.trim().toLowerCase();
|
|
const user = await User.findOne({ email_lc });
|
|
if (!user) {
|
|
throw new Error(`user not found: ${email}`);
|
|
}
|
|
|
|
user.passwordSalt = uuidv4();
|
|
user.password = await CryptoService.maskPassword(
|
|
user.passwordSalt,
|
|
password
|
|
);
|
|
await user.save();
|
|
|
|
this.log.info(`user ${email} password changed`);
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
await this.startServices();
|
|
}
|
|
|
|
async startServices(): Promise<void> {
|
|
await ApiClientService.start();
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
try {
|
|
const cli = new DtpWebCli();
|
|
await cli.start();
|
|
await cli.run(process.argv.slice(2));
|
|
process.exit(0);
|
|
} catch (error) {
|
|
console.error((error as Error).message);
|
|
process.exit(-1);
|
|
}
|
|
})();
|