mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 08:06:00 +00:00
0629e1bc50
integrated role selection prompt during user creation and added an auth-enabled check to ensure correct configuration before executing commands. adjusted CLI commands to include role assignment for newly created users.
195 lines
5.0 KiB
TypeScript
195 lines
5.0 KiB
TypeScript
import {
|
|
isCancel as $isCancel,
|
|
log as $log,
|
|
password as $password,
|
|
text as $text,
|
|
select as $select,
|
|
} from "@clack/prompts";
|
|
import type { App } from "App";
|
|
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
|
import { makeAppFromEnv } from "cli/commands/run";
|
|
import type { CliCommand } from "cli/types";
|
|
import { Argument } from "commander";
|
|
import { $console, isBun } from "bknd/utils";
|
|
import c from "picocolors";
|
|
import { withConfigOptions, type WithConfigOptions } from "cli/utils/options";
|
|
|
|
export const user: CliCommand = (program) => {
|
|
withConfigOptions(program.command("user"))
|
|
.description("create/update users, or generate a token (auth)")
|
|
.addArgument(
|
|
new Argument("<action>", "action to perform").choices(["create", "update", "token"]),
|
|
)
|
|
.action(action);
|
|
};
|
|
|
|
async function action(action: "create" | "update" | "token", options: WithConfigOptions) {
|
|
const app = await makeAppFromEnv({
|
|
config: options.config,
|
|
dbUrl: options.dbUrl,
|
|
server: "node",
|
|
});
|
|
|
|
if (!app.module.auth.enabled) {
|
|
$log.error("Auth is not enabled");
|
|
process.exit(1);
|
|
}
|
|
|
|
switch (action) {
|
|
case "create":
|
|
await create(app, options);
|
|
break;
|
|
case "update":
|
|
await update(app, options);
|
|
break;
|
|
case "token":
|
|
await token(app, options);
|
|
break;
|
|
}
|
|
}
|
|
|
|
async function create(app: App, options: any) {
|
|
const auth = app.module.auth;
|
|
let role: string | null = null;
|
|
const roles = Object.keys(auth.config.roles ?? {});
|
|
|
|
const strategy = auth.authenticator.strategy("password") as PasswordStrategy;
|
|
if (roles.length > 0) {
|
|
role = (await $select({
|
|
message: "Select role",
|
|
options: [
|
|
{
|
|
value: null,
|
|
label: "<none>",
|
|
hint: "No role will be assigned to the user",
|
|
},
|
|
...roles.map((role) => ({
|
|
value: role,
|
|
label: role,
|
|
})),
|
|
],
|
|
})) as any;
|
|
if ($isCancel(role)) process.exit(1);
|
|
}
|
|
|
|
if (!strategy) {
|
|
$log.error("Password strategy not configured");
|
|
process.exit(1);
|
|
}
|
|
|
|
const email = await $text({
|
|
message: "Enter email",
|
|
validate: (v) => {
|
|
if (!v.includes("@")) {
|
|
return "Invalid email";
|
|
}
|
|
return;
|
|
},
|
|
});
|
|
if ($isCancel(email)) process.exit(1);
|
|
|
|
const password = await $password({
|
|
message: "Enter password",
|
|
validate: (v) => {
|
|
if (v.length < 3) {
|
|
return "Invalid password";
|
|
}
|
|
return;
|
|
},
|
|
});
|
|
if ($isCancel(password)) process.exit(1);
|
|
|
|
try {
|
|
const created = await app.createUser({
|
|
email,
|
|
password: await strategy.hash(password as string),
|
|
role,
|
|
});
|
|
$log.success(`Created user: ${c.cyan(created.email)}`);
|
|
process.exit(0);
|
|
} catch (e) {
|
|
$log.error("Error creating user");
|
|
$console.error(e);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function update(app: App, options: any) {
|
|
const config = app.module.auth.toJSON(true);
|
|
|
|
const email = (await $text({
|
|
message: "Which user? Enter email",
|
|
validate: (v) => {
|
|
if (!v.includes("@")) {
|
|
return "Invalid email";
|
|
}
|
|
return;
|
|
},
|
|
})) as string;
|
|
if ($isCancel(email)) process.exit(1);
|
|
|
|
const { data: user } = await app.modules
|
|
.ctx()
|
|
.em.repository(config.entity_name as "users")
|
|
.findOne({ email });
|
|
if (!user) {
|
|
$log.error("User not found");
|
|
process.exit(1);
|
|
}
|
|
$log.info(`User found: ${c.cyan(user.email)}`);
|
|
|
|
const password = await $password({
|
|
message: "New Password?",
|
|
validate: (v) => {
|
|
if (v.length < 3) {
|
|
return "Invalid password";
|
|
}
|
|
return;
|
|
},
|
|
});
|
|
if ($isCancel(password)) process.exit(1);
|
|
|
|
if (await app.module.auth.changePassword(user.id, password)) {
|
|
$log.success(`Updated user: ${c.cyan(user.email)}`);
|
|
process.exit(0);
|
|
} else {
|
|
$log.error("Error updating user");
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function token(app: App, options: any) {
|
|
if (isBun()) {
|
|
$log.error("Please use node to generate tokens");
|
|
process.exit(1);
|
|
}
|
|
|
|
const config = app.module.auth.toJSON(true);
|
|
const users_entity = config.entity_name as "users";
|
|
const em = app.modules.ctx().em;
|
|
|
|
const email = (await $text({
|
|
message: "Which user? Enter email",
|
|
validate: (v) => {
|
|
if (!v.includes("@")) {
|
|
return "Invalid email";
|
|
}
|
|
return;
|
|
},
|
|
})) as string;
|
|
if ($isCancel(email)) process.exit(1);
|
|
|
|
const { data: user } = await em.repository(users_entity).findOne({ email });
|
|
if (!user) {
|
|
$log.error("User not found");
|
|
process.exit(1);
|
|
}
|
|
$log.info(`User found: ${c.cyan(user.email)}`);
|
|
|
|
// biome-ignore lint/suspicious/noConsoleLog:
|
|
console.log(
|
|
`\n${c.dim("Token:")}\n${c.yellow(await app.module.auth.authenticator.jwt(user))}\n`,
|
|
);
|
|
process.exit(0);
|
|
}
|