@naisys/erp 3.0.0-beta.22 → 3.0.0-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,9 +33,9 @@
33
33
  <meta name="format-detection" content="telephone=no" />
34
34
 
35
35
  <title>NAISYS ERP</title>
36
- <script type="module" crossorigin src="/erp/assets/index-BJzK1WXg.js"></script>
36
+ <script type="module" crossorigin src="/erp/assets/index-DycEn-R_.js"></script>
37
37
  <link rel="modulepreload" crossorigin href="/erp/assets/rolldown-runtime-CvHMtSRF.js">
38
- <link rel="modulepreload" crossorigin href="/erp/assets/vendor-DlmcA82d.js">
38
+ <link rel="modulepreload" crossorigin href="/erp/assets/vendor-MNFI7PUp.js">
39
39
  <link rel="stylesheet" crossorigin href="/erp/assets/vendor-CLUPjUnv.css">
40
40
  <link rel="stylesheet" crossorigin href="/erp/assets/index-CSiMTJfw.css">
41
41
  </head>
package/dist/erpServer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "dotenv/config";
2
2
  import "./schema-registry.js";
3
- import { cwdWithTilde, ensureDotEnv, expandNaisysFolder, runSetupWizard, } from "@naisys/common-node";
3
+ import { cwdWithTilde, ensureDotEnv, expandNaisysFolder, promptSuperAdminPassword, runSetupWizard, } from "@naisys/common-node";
4
4
  expandNaisysFolder();
5
5
  // Important to load dotenv before any other imports, to ensure environment variables are available
6
6
  import cookie from "@fastify/cookie";
@@ -9,10 +9,10 @@ import multipart from "@fastify/multipart";
9
9
  import { fastifyRateLimit as rateLimit } from "@fastify/rate-limit";
10
10
  import staticFiles from "@fastify/static";
11
11
  import swagger from "@fastify/swagger";
12
- import { commonErrorHandler, registerLenientJsonParser, registerSecurityHeaders, } from "@naisys/common";
12
+ import { commonErrorHandler, registerLenientJsonParser, registerSecurityHeaders, SUPER_ADMIN_USERNAME, } from "@naisys/common";
13
13
  import { createFileLogger } from "@naisys/common-node";
14
14
  import { createHubDatabaseClient, deployPrismaMigrations, } from "@naisys/hub-database";
15
- import { createSupervisorDatabaseClient, handleResetPassword, } from "@naisys/supervisor-database";
15
+ import { createSupervisorDatabaseClient } from "@naisys/supervisor-database";
16
16
  import Fastify from "fastify";
17
17
  import { jsonSchemaTransform, jsonSchemaTransformObject, serializerCompiler, validatorCompiler, } from "fastify-type-provider-zod";
18
18
  import path from "path";
@@ -23,7 +23,7 @@ import { ERP_DB_VERSION, erpDbPath } from "./dbConfig.js";
23
23
  import { initErpDb } from "./erpDb.js";
24
24
  import { erpRoutes } from "./erpRoutes.js";
25
25
  import { isSupervisorAuth } from "./supervisorAuth.js";
26
- import { ensureLocalSuperAdmin, ensureSupervisorSuperAdmin, resetLocalPassword, } from "./userService.js";
26
+ import { ensureLocalSuperAdmin, ensureSupervisorSuperAdmin, } from "./userService.js";
27
27
  export { enableSupervisorAuth } from "./supervisorAuth.js";
28
28
  const __filename = fileURLToPath(import.meta.url);
29
29
  const __dirname = path.dirname(__filename);
@@ -31,7 +31,7 @@ const __dirname = path.dirname(__filename);
31
31
  * Fastify plugin that registers ERP routes and static files.
32
32
  * Can be used standalone or registered inside another Fastify app (e.g. supervisor).
33
33
  */
34
- export const erpPlugin = async (fastify) => {
34
+ export const erpPlugin = async (fastify, opts) => {
35
35
  const isProd = process.env.NODE_ENV === "production";
36
36
  // Cookie plugin (guard for supervisor embedding)
37
37
  if (!fastify.hasDecorator("parseCookie")) {
@@ -66,7 +66,7 @@ export const erpPlugin = async (fastify) => {
66
66
  await ensureSupervisorSuperAdmin();
67
67
  }
68
68
  else {
69
- await ensureLocalSuperAdmin();
69
+ await ensureLocalSuperAdmin(opts.superAdminPassword);
70
70
  }
71
71
  fastify.setErrorHandler(commonErrorHandler);
72
72
  registerAuthMiddleware(fastify);
@@ -125,7 +125,7 @@ export const erpPlugin = async (fastify) => {
125
125
  });
126
126
  }
127
127
  };
128
- async function startServer() {
128
+ async function startServer(wizardRan) {
129
129
  const isProd = process.env.NODE_ENV === "production";
130
130
  const fastify = Fastify({
131
131
  pluginTimeout: 60_000,
@@ -159,7 +159,10 @@ async function startServer() {
159
159
  fastify.get("/", { schema: { hide: true } }, async (_request, reply) => {
160
160
  return reply.redirect("/erp/");
161
161
  });
162
- await fastify.register(erpPlugin);
162
+ const superAdminPassword = wizardRan && !isSupervisorAuth()
163
+ ? await promptSuperAdminPassword("ERP Setup")
164
+ : undefined;
165
+ await fastify.register(erpPlugin, { superAdminPassword });
163
166
  const port = Number(process.env.SERVER_PORT) || 3302;
164
167
  const host = isProd ? "0.0.0.0" : "localhost";
165
168
  try {
@@ -167,6 +170,9 @@ async function startServer() {
167
170
  console.log(`[ERP] Running on http://${host}:${port}/erp`);
168
171
  console.log(`[ERP] API Reference: http://${host}:${port}/erp/api-reference`);
169
172
  console.log(`[ERP] Auth mode: ${isSupervisorAuth() ? "supervisor" : "standalone"}`);
173
+ if (!isSupervisorAuth()) {
174
+ console.log(`[ERP] Sign in as '${SUPER_ADMIN_USERNAME}' with the password set during setup. Use --setup to change it.`);
175
+ }
170
176
  }
171
177
  catch (err) {
172
178
  console.error("[ERP] Failed to start:", err);
@@ -191,43 +197,13 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
191
197
  ],
192
198
  };
193
199
  const erpExampleUrl = new URL("../.env.example", import.meta.url);
194
- if (process.argv.includes("--reset-password")) {
195
- const usernameIdx = process.argv.indexOf("--username");
196
- const passwordIdx = process.argv.indexOf("--password");
197
- const username = usernameIdx !== -1 ? process.argv[usernameIdx + 1] : undefined;
198
- const password = passwordIdx !== -1 ? process.argv[passwordIdx + 1] : undefined;
199
- await initErpDb();
200
- if (isSupervisorAuth()) {
201
- void handleResetPassword({
202
- findLocalUser: async (username) => {
203
- const prisma = (await import("./erpDb.js")).default;
204
- const user = await prisma.user.findUnique({ where: { username } });
205
- return user
206
- ? { id: user.id, username: user.username, uuid: user.uuid }
207
- : null;
208
- },
209
- updateLocalPassword: async (userId, passwordHash) => {
210
- const prisma = (await import("./erpDb.js")).default;
211
- await prisma.user.update({
212
- where: { id: userId },
213
- data: { passwordHash },
214
- });
215
- },
216
- username,
217
- password,
218
- });
219
- }
220
- else {
221
- void resetLocalPassword();
222
- }
223
- }
224
- else {
225
- if (process.argv.includes("--setup")) {
226
- await runSetupWizard(path.resolve(".env"), erpExampleUrl, erpWizardConfig);
227
- expandNaisysFolder();
228
- }
229
- await ensureDotEnv(erpExampleUrl, erpWizardConfig);
230
- void startServer();
200
+ let wizardRan = false;
201
+ if (process.argv.includes("--setup")) {
202
+ wizardRan = await runSetupWizard(path.resolve(".env"), erpExampleUrl, erpWizardConfig);
203
+ expandNaisysFolder();
231
204
  }
205
+ wizardRan =
206
+ (await ensureDotEnv(erpExampleUrl, erpWizardConfig)) || wizardRan;
207
+ void startServer(wizardRan);
232
208
  }
233
209
  //# sourceMappingURL=erpServer.js.map
@@ -2,24 +2,30 @@ import { SUPER_ADMIN_USERNAME } from "@naisys/common";
2
2
  import { ensureSuperAdmin } from "@naisys/supervisor-database";
3
3
  import bcrypt from "bcryptjs";
4
4
  import { randomBytes, randomUUID } from "crypto";
5
- import readline from "readline/promises";
6
5
  import erpDb from "./erpDb.js";
7
6
  const SALT_ROUNDS = 10;
8
7
  /**
9
8
  * Ensure a superadmin user exists in the local ERP database.
9
+ * If a password is supplied, it is used on create and updates the existing one if present.
10
10
  * For standalone mode (no supervisor auth).
11
11
  */
12
- export async function ensureLocalSuperAdmin() {
12
+ export async function ensureLocalSuperAdmin(password) {
13
13
  const existing = await erpDb.user.findUnique({
14
14
  where: { username: SUPER_ADMIN_USERNAME },
15
15
  });
16
16
  if (existing) {
17
- // Ensure superadmin has erp_admin permission
18
17
  await ensureErpAdminPermission(existing.id);
18
+ if (password) {
19
+ const hash = await bcrypt.hash(password, SALT_ROUNDS);
20
+ await erpDb.user.update({
21
+ where: { id: existing.id },
22
+ data: { passwordHash: hash },
23
+ });
24
+ }
19
25
  }
20
26
  else {
21
- const password = randomUUID().slice(0, 8);
22
- const hash = await bcrypt.hash(password, SALT_ROUNDS);
27
+ const finalPassword = password || randomUUID().slice(0, 8);
28
+ const hash = await bcrypt.hash(finalPassword, SALT_ROUNDS);
23
29
  const user = await erpDb.user.create({
24
30
  data: {
25
31
  uuid: randomUUID(),
@@ -29,8 +35,10 @@ export async function ensureLocalSuperAdmin() {
29
35
  },
30
36
  });
31
37
  await ensureErpAdminPermission(user.id);
32
- console.log(`\n ${SUPER_ADMIN_USERNAME} user created. Password: ${password}`);
33
- console.log(` Change it via --reset-password\n`);
38
+ if (!password) {
39
+ console.log(`\n ${SUPER_ADMIN_USERNAME} user created. Password: ${finalPassword}`);
40
+ console.log(` Change it via the admin UI or with --setup\n`);
41
+ }
34
42
  }
35
43
  // Warn if agent users exist without supervisor auth
36
44
  const agentCount = await erpDb.user.count({ where: { isAgent: true } });
@@ -66,9 +74,6 @@ export async function ensureSupervisorSuperAdmin() {
66
74
  if (localSuperAdmin) {
67
75
  await ensureErpAdminPermission(localSuperAdmin.id);
68
76
  }
69
- if (result.created) {
70
- console.log(`[ERP] ${SUPER_ADMIN_USERNAME} user created. Password: ${result.generatedPassword}`);
71
- }
72
77
  }
73
78
  /**
74
79
  * Ensure a user has the erp_admin permission.
@@ -83,36 +88,4 @@ export async function ensureErpAdminPermission(userId) {
83
88
  });
84
89
  }
85
90
  }
86
- /**
87
- * Interactive CLI to reset a local user's password.
88
- * For standalone mode (no supervisor auth).
89
- */
90
- export async function resetLocalPassword() {
91
- const rl = readline.createInterface({
92
- input: process.stdin,
93
- output: process.stdout,
94
- });
95
- try {
96
- const username = await rl.question("Username: ");
97
- const user = await erpDb.user.findUnique({ where: { username } });
98
- if (!user) {
99
- console.error(`User '${username}' not found.`);
100
- process.exit(1);
101
- }
102
- const password = await rl.question("New password: ");
103
- if (password.length < 6) {
104
- console.error("Password must be at least 6 characters.");
105
- process.exit(1);
106
- }
107
- const hash = await bcrypt.hash(password, SALT_ROUNDS);
108
- await erpDb.user.update({
109
- where: { id: user.id },
110
- data: { passwordHash: hash },
111
- });
112
- console.log(`Password reset for '${username}'.`);
113
- }
114
- finally {
115
- rl.close();
116
- }
117
- }
118
91
  //# sourceMappingURL=userService.js.map