mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 20:37:21 +00:00
cli: user: add token generation (#140)
* cli: user: add token generation * cli: user: add token generation * cli: user: check for value being cancel before continuing
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { type DB, Exception } from "core";
|
import { type DB, Exception, type PrimaryFieldType } from "core";
|
||||||
import { addFlashMessage } from "core/server/flash";
|
import { addFlashMessage } from "core/server/flash";
|
||||||
import {
|
import {
|
||||||
type Static,
|
type Static,
|
||||||
@@ -14,6 +14,7 @@ import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
|
|||||||
import { sign, verify } from "hono/jwt";
|
import { sign, verify } from "hono/jwt";
|
||||||
import type { CookieOptions } from "hono/utils/cookie";
|
import type { CookieOptions } from "hono/utils/cookie";
|
||||||
import type { ServerEnv } from "modules/Controller";
|
import type { ServerEnv } from "modules/Controller";
|
||||||
|
import { pick } from "lodash-es";
|
||||||
|
|
||||||
type Input = any; // workaround
|
type Input = any; // workaround
|
||||||
export type JWTPayload = Parameters<typeof sign>[0];
|
export type JWTPayload = Parameters<typeof sign>[0];
|
||||||
@@ -37,11 +38,10 @@ export interface Strategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: number;
|
id: PrimaryFieldType;
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
|
||||||
password: string;
|
password: string;
|
||||||
role: string;
|
role?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProfileExchange = {
|
export type ProfileExchange = {
|
||||||
@@ -158,13 +158,8 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @todo: add jwt tests
|
// @todo: add jwt tests
|
||||||
async jwt(user: Omit<User, "password">): Promise<string> {
|
async jwt(_user: Omit<User, "password">): Promise<string> {
|
||||||
const prohibited = ["password"];
|
const user = pick(_user, this.config.jwt.fields);
|
||||||
for (const prop of prohibited) {
|
|
||||||
if (prop in user) {
|
|
||||||
throw new Error(`Property "${prop}" is prohibited`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: JWTPayload = {
|
const payload: JWTPayload = {
|
||||||
...user,
|
...user,
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
import { password as $password, text as $text } from "@clack/prompts";
|
import {
|
||||||
|
password as $password,
|
||||||
|
text as $text,
|
||||||
|
log as $log,
|
||||||
|
isCancel as $isCancel,
|
||||||
|
} from "@clack/prompts";
|
||||||
import type { App } from "App";
|
import type { App } from "App";
|
||||||
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
||||||
import { makeConfigApp } from "cli/commands/run";
|
import { makeConfigApp } from "cli/commands/run";
|
||||||
import { getConfigPath } from "cli/commands/run/platform";
|
import { getConfigPath } from "cli/commands/run/platform";
|
||||||
import type { CliBkndConfig, CliCommand } from "cli/types";
|
import type { CliBkndConfig, CliCommand } from "cli/types";
|
||||||
import { Argument } from "commander";
|
import { Argument } from "commander";
|
||||||
|
import { $console } from "core";
|
||||||
|
import c from "picocolors";
|
||||||
|
|
||||||
export const user: CliCommand = (program) => {
|
export const user: CliCommand = (program) => {
|
||||||
program
|
program
|
||||||
.command("user")
|
.command("user")
|
||||||
.description("create and update user (auth)")
|
.description("create/update users, or generate a token (auth)")
|
||||||
.addArgument(new Argument("<action>", "action to perform").choices(["create", "update"]))
|
.addArgument(
|
||||||
|
new Argument("<action>", "action to perform").choices(["create", "update", "token"]),
|
||||||
|
)
|
||||||
.action(action);
|
.action(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function action(action: "create" | "update", options: any) {
|
async function action(action: "create" | "update" | "token", options: any) {
|
||||||
const configFilePath = await getConfigPath();
|
const configFilePath = await getConfigPath();
|
||||||
if (!configFilePath) {
|
if (!configFilePath) {
|
||||||
console.error("config file not found");
|
console.error("config file not found");
|
||||||
@@ -31,6 +40,9 @@ async function action(action: "create" | "update", options: any) {
|
|||||||
case "update":
|
case "update":
|
||||||
await update(app, options);
|
await update(app, options);
|
||||||
break;
|
break;
|
||||||
|
case "token":
|
||||||
|
await token(app, options);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +50,8 @@ async function create(app: App, options: any) {
|
|||||||
const strategy = app.module.auth.authenticator.strategy("password") as PasswordStrategy;
|
const strategy = app.module.auth.authenticator.strategy("password") as PasswordStrategy;
|
||||||
|
|
||||||
if (!strategy) {
|
if (!strategy) {
|
||||||
throw new Error("Password strategy not configured");
|
$log.error("Password strategy not configured");
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = await $text({
|
const email = await $text({
|
||||||
@@ -50,6 +63,7 @@ async function create(app: App, options: any) {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if ($isCancel(email)) process.exit(1);
|
||||||
|
|
||||||
const password = await $password({
|
const password = await $password({
|
||||||
message: "Enter password",
|
message: "Enter password",
|
||||||
@@ -60,20 +74,17 @@ async function create(app: App, options: any) {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if ($isCancel(password)) process.exit(1);
|
||||||
if (typeof email !== "string" || typeof password !== "string") {
|
|
||||||
console.log("Cancelled");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const created = await app.createUser({
|
const created = await app.createUser({
|
||||||
email,
|
email,
|
||||||
password: await strategy.hash(password as string),
|
password: await strategy.hash(password as string),
|
||||||
});
|
});
|
||||||
console.log("Created:", created);
|
$log.success(`Created user: ${c.cyan(created.email)}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error", e);
|
$log.error("Error creating user");
|
||||||
|
$console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,17 +103,14 @@ async function update(app: App, options: any) {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
})) as string;
|
})) as string;
|
||||||
if (typeof email !== "string") {
|
if ($isCancel(email)) process.exit(1);
|
||||||
console.log("Cancelled");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: user } = await em.repository(users_entity).findOne({ email });
|
const { data: user } = await em.repository(users_entity).findOne({ email });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.log("User not found");
|
$log.error("User not found");
|
||||||
process.exit(0);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log("User found:", user);
|
$log.info(`User found: ${c.cyan(user.email)}`);
|
||||||
|
|
||||||
const password = await $password({
|
const password = await $password({
|
||||||
message: "New Password?",
|
message: "New Password?",
|
||||||
@@ -113,10 +121,7 @@ async function update(app: App, options: any) {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (typeof password !== "string") {
|
if ($isCancel(password)) process.exit(1);
|
||||||
console.log("Cancelled");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
function togglePw(visible: boolean) {
|
function togglePw(visible: boolean) {
|
||||||
@@ -134,8 +139,37 @@ async function update(app: App, options: any) {
|
|||||||
});
|
});
|
||||||
togglePw(false);
|
togglePw(false);
|
||||||
|
|
||||||
console.log("Updated:", user);
|
$log.success(`Updated user: ${c.cyan(user.email)}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error", e);
|
$log.error("Error updating user");
|
||||||
|
$console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function token(app: App, options: any) {
|
||||||
|
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)}`);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${c.dim("Token:")}\n${c.yellow(await app.module.auth.authenticator.jwt(user))}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user