@naisys/erp 3.0.0-beta.9 → 3.0.0

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.
Files changed (83) hide show
  1. package/.env.example +5 -0
  2. package/client-dist/assets/index-CSiMTJfw.css +14 -0
  3. package/client-dist/assets/index-D6lSIioV.js +11167 -0
  4. package/client-dist/assets/rolldown-runtime-CvHMtSRF.js +33 -0
  5. package/client-dist/assets/vendor-CJ0ET9hP.js +75181 -0
  6. package/client-dist/assets/vendor-CLUPjUnv.css +8747 -0
  7. package/client-dist/favicon-16x16.png +0 -0
  8. package/client-dist/favicon-32x32.png +0 -0
  9. package/client-dist/favicon.ico +0 -0
  10. package/client-dist/index.html +17 -2
  11. package/dist/{dbConfig.js → database/dbConfig.js} +1 -1
  12. package/dist/{erpDb.js → database/erpDb.js} +1 -1
  13. package/dist/erpRoutes.js +115 -0
  14. package/dist/erpServer.js +85 -161
  15. package/dist/error-handler.js +3 -0
  16. package/dist/generated/prisma/internal/class.js +4 -4
  17. package/dist/generated/prisma/internal/prismaNamespace.js +3 -1
  18. package/dist/middleware/auth-middleware.js +146 -0
  19. package/dist/route-helpers.js +2 -2
  20. package/dist/routes/admin.js +15 -7
  21. package/dist/routes/audit.js +1 -1
  22. package/dist/routes/{item-fields.js → items/item-fields.js} +24 -22
  23. package/dist/routes/{item-instances.js → items/item-instances.js} +42 -24
  24. package/dist/routes/{items.js → items/items.js} +35 -33
  25. package/dist/routes/{operation-dependencies.js → operations/operation-dependencies.js} +6 -6
  26. package/dist/routes/{operation-field-refs.js → operations/operation-field-refs.js} +6 -6
  27. package/dist/routes/{operation-run-comments.js → operations/operation-run-comments.js} +5 -5
  28. package/dist/routes/{operation-run-transitions.js → operations/operation-run-transitions.js} +29 -13
  29. package/dist/routes/{operation-runs.js → operations/operation-runs.js} +48 -10
  30. package/dist/routes/{operations.js → operations/operations.js} +6 -6
  31. package/dist/routes/{order-revision-transitions.js → orders/order-revision-transitions.js} +4 -4
  32. package/dist/routes/{order-revisions.js → orders/order-revisions.js} +6 -6
  33. package/dist/routes/{order-run-transitions.js → orders/order-run-transitions.js} +11 -5
  34. package/dist/routes/{order-runs.js → orders/order-runs.js} +7 -5
  35. package/dist/routes/{orders.js → orders/orders.js} +15 -11
  36. package/dist/routes/{dispatch.js → production/dispatch.js} +88 -7
  37. package/dist/routes/{inventory.js → production/inventory.js} +33 -10
  38. package/dist/routes/{labor-tickets.js → production/labor-tickets.js} +7 -7
  39. package/dist/routes/{work-centers.js → production/work-centers.js} +29 -29
  40. package/dist/routes/root.js +1 -1
  41. package/dist/routes/{step-field-attachments.js → steps/step-field-attachments.js} +8 -8
  42. package/dist/routes/{step-fields.js → steps/step-fields.js} +6 -6
  43. package/dist/routes/{step-run-fields.js → steps/step-run-fields.js} +9 -9
  44. package/dist/routes/{step-run-transitions.js → steps/step-run-transitions.js} +6 -6
  45. package/dist/routes/{step-runs.js → steps/step-runs.js} +7 -7
  46. package/dist/routes/{steps.js → steps/steps.js} +5 -5
  47. package/dist/routes/{auth.js → users/auth.js} +11 -23
  48. package/dist/routes/{user-permissions.js → users/user-permissions.js} +21 -7
  49. package/dist/routes/{users.js → users/users.js} +42 -20
  50. package/dist/services/attachment-service.js +2 -2
  51. package/dist/services/{item-instance-service.js → inventory/item-instance-service.js} +2 -2
  52. package/dist/services/{item-service.js → inventory/item-service.js} +2 -2
  53. package/dist/services/{operation-dependency-service.js → operations/operation-dependency-service.js} +1 -1
  54. package/dist/services/{operation-run-comment-service.js → operations/operation-run-comment-service.js} +1 -1
  55. package/dist/services/{operation-run-service.js → operations/operation-run-service.js} +15 -4
  56. package/dist/services/{operation-service.js → operations/operation-service.js} +2 -2
  57. package/dist/services/{step-run-service.js → operations/step-run-service.js} +1 -1
  58. package/dist/services/{step-service.js → operations/step-service.js} +2 -2
  59. package/dist/services/{order-revision-service.js → orders/order-revision-service.js} +4 -5
  60. package/dist/services/{order-run-service.js → orders/order-run-service.js} +68 -22
  61. package/dist/services/{order-service.js → orders/order-service.js} +11 -2
  62. package/dist/services/{revision-diff-service.js → orders/revision-diff-service.js} +11 -10
  63. package/dist/services/{field-ref-service.js → production/field-ref-service.js} +1 -1
  64. package/dist/services/{field-service.js → production/field-service.js} +2 -2
  65. package/dist/services/{field-value-service.js → production/field-value-service.js} +27 -3
  66. package/dist/services/production/labor-ticket-backfill.js +67 -0
  67. package/dist/services/{labor-ticket-service.js → production/labor-ticket-service.js} +21 -15
  68. package/dist/services/{work-center-service.js → production/work-center-service.js} +2 -2
  69. package/dist/services/user-service.js +94 -28
  70. package/dist/version.js +12 -0
  71. package/npm-shrinkwrap.json +3000 -0
  72. package/package.json +11 -9
  73. package/prisma/migrations/20260427010000_hash_user_api_keys/migration.sql +10 -0
  74. package/prisma/migrations/20260427020000_nullable_user_password_hash/migration.sql +39 -0
  75. package/prisma/migrations/20260517000000_add_op_run_tokens/migration.sql +2 -0
  76. package/prisma/schema.prisma +4 -2
  77. package/client-dist/assets/index-45dVo30p.css +0 -1
  78. package/client-dist/assets/index-C9uuPHLH.js +0 -168
  79. package/dist/auth-middleware.js +0 -203
  80. package/dist/userService.js +0 -118
  81. /package/bin/{naisys-erp → naisys-erp.js} +0 -0
  82. /package/dist/{supervisorAuth.js → middleware/supervisorAuth.js} +0 -0
  83. /package/dist/{audit.js → services/audit.js} +0 -0
@@ -1,203 +0,0 @@
1
- import { AuthCache } from "@naisys/common";
2
- import { extractBearerToken, hashToken, SESSION_COOKIE_NAME, } from "@naisys/common-node";
3
- import { findAgentByApiKey } from "@naisys/hub-database";
4
- import { findSession, findUserByApiKey } from "@naisys/supervisor-database";
5
- import erpDb from "./erpDb.js";
6
- import { isSupervisorAuth } from "./supervisorAuth.js";
7
- const PUBLIC_PREFIXES = ["/erp/api/auth/login", "/erp/api/client-config"];
8
- export const authCache = new AuthCache();
9
- async function loadPermissions(userId) {
10
- const perms = await erpDb.userPermission.findMany({
11
- where: { userId },
12
- select: { permission: true },
13
- });
14
- return perms.map((p) => p.permission);
15
- }
16
- export function hasPermission(user, permission) {
17
- if (!user)
18
- return false;
19
- return (user.permissions.includes(permission) ||
20
- user.permissions.includes("erp_admin"));
21
- }
22
- export function requirePermission(permission) {
23
- return async (request, reply) => {
24
- if (!request.erpUser) {
25
- reply.status(401).send({
26
- statusCode: 401,
27
- error: "Unauthorized",
28
- message: "Authentication required",
29
- });
30
- return;
31
- }
32
- if (!hasPermission(request.erpUser, permission)) {
33
- reply.status(403).send({
34
- statusCode: 403,
35
- error: "Forbidden",
36
- message: `Permission '${permission}' required`,
37
- });
38
- return;
39
- }
40
- };
41
- }
42
- function isPublicRoute(url) {
43
- // Exact match: API root
44
- if (url === "/erp/api/" || url === "/erp/api")
45
- return true;
46
- // Prefix matches
47
- for (const prefix of PUBLIC_PREFIXES) {
48
- if (url.startsWith(prefix))
49
- return true;
50
- }
51
- // Schema routes
52
- if (url.startsWith("/erp/api/schemas"))
53
- return true;
54
- // Non-ERP-API paths (static files, supervisor routes, etc.)
55
- if (!url.startsWith("/erp/api"))
56
- return true;
57
- return false;
58
- }
59
- export function registerAuthMiddleware(fastify) {
60
- const publicRead = process.env.PUBLIC_READ === "true";
61
- fastify.decorateRequest("erpUser", undefined);
62
- fastify.addHook("onRequest", async (request, reply) => {
63
- const token = request.cookies?.[SESSION_COOKIE_NAME];
64
- if (token) {
65
- const tokenHash = hashToken(token);
66
- const cacheKey = `cookie:${tokenHash}`;
67
- const cached = authCache.get(cacheKey);
68
- if (cached !== undefined) {
69
- // Cache hit (valid or negative)
70
- if (cached)
71
- request.erpUser = cached;
72
- }
73
- else if (isSupervisorAuth()) {
74
- // SSO mode: supervisor DB is source of truth for sessions
75
- const session = await findSession(tokenHash);
76
- if (session) {
77
- let localUser = await erpDb.user.findUnique({
78
- where: { uuid: session.uuid },
79
- });
80
- if (!localUser) {
81
- localUser = await erpDb.user.create({
82
- data: {
83
- uuid: session.uuid,
84
- username: session.username,
85
- passwordHash: session.passwordHash,
86
- },
87
- });
88
- }
89
- const permissions = await loadPermissions(localUser.id);
90
- const erpUser = {
91
- id: localUser.id,
92
- username: localUser.username,
93
- permissions,
94
- };
95
- authCache.set(cacheKey, erpUser);
96
- request.erpUser = erpUser;
97
- }
98
- else {
99
- authCache.set(cacheKey, null);
100
- }
101
- }
102
- else {
103
- // Standalone mode: local session only
104
- const session = await erpDb.session.findUnique({
105
- where: {
106
- tokenHash,
107
- expiresAt: { gt: new Date() },
108
- },
109
- include: { user: true },
110
- });
111
- if (session) {
112
- const permissions = await loadPermissions(session.user.id);
113
- const erpUser = {
114
- id: session.user.id,
115
- username: session.user.username,
116
- permissions,
117
- };
118
- authCache.set(cacheKey, erpUser);
119
- request.erpUser = erpUser;
120
- }
121
- else {
122
- authCache.set(cacheKey, null);
123
- }
124
- }
125
- }
126
- // API key auth (for agents / machine-to-machine)
127
- if (!request.erpUser) {
128
- const apiKey = extractBearerToken(request.headers.authorization);
129
- if (apiKey) {
130
- const apiKeyHash = hashToken(apiKey);
131
- const cacheKey = `apikey:${apiKeyHash}`;
132
- const cached = authCache.get(cacheKey);
133
- if (cached !== undefined) {
134
- if (cached)
135
- request.erpUser = cached;
136
- }
137
- else if (isSupervisorAuth()) {
138
- // SSO mode: try supervisor DB (human users), then hub DB (agents)
139
- const match = (await findUserByApiKey(apiKey)) ??
140
- (await findAgentByApiKey(apiKey));
141
- if (match) {
142
- let localUser = await erpDb.user.findUnique({
143
- where: { uuid: match.uuid },
144
- });
145
- if (!localUser) {
146
- localUser = await erpDb.user.create({
147
- data: {
148
- uuid: match.uuid,
149
- username: match.username,
150
- passwordHash: "!api-key-only",
151
- isAgent: true,
152
- },
153
- });
154
- }
155
- const permissions = await loadPermissions(localUser.id);
156
- const erpUser = {
157
- id: localUser.id,
158
- username: localUser.username,
159
- permissions,
160
- };
161
- authCache.set(cacheKey, erpUser);
162
- request.erpUser = erpUser;
163
- }
164
- else {
165
- authCache.set(cacheKey, null);
166
- }
167
- }
168
- else {
169
- // Standalone mode: check local ERP user table
170
- const localUser = await erpDb.user.findUnique({
171
- where: { apiKey },
172
- });
173
- if (localUser) {
174
- const permissions = await loadPermissions(localUser.id);
175
- const erpUser = {
176
- id: localUser.id,
177
- username: localUser.username,
178
- permissions,
179
- };
180
- authCache.set(cacheKey, erpUser);
181
- request.erpUser = erpUser;
182
- }
183
- else {
184
- authCache.set(cacheKey, null);
185
- }
186
- }
187
- }
188
- }
189
- // Check if auth is required
190
- if (request.erpUser)
191
- return; // Authenticated, always allowed
192
- if (isPublicRoute(request.url))
193
- return; // Public route
194
- if (publicRead && request.method === "GET")
195
- return; // Public read mode
196
- reply.status(401).send({
197
- statusCode: 401,
198
- error: "Unauthorized",
199
- message: "Authentication required",
200
- });
201
- });
202
- }
203
- //# sourceMappingURL=auth-middleware.js.map
@@ -1,118 +0,0 @@
1
- import { SUPER_ADMIN_USERNAME } from "@naisys/common";
2
- import { ensureSuperAdmin } from "@naisys/supervisor-database";
3
- import bcrypt from "bcryptjs";
4
- import { randomBytes, randomUUID } from "crypto";
5
- import readline from "readline/promises";
6
- import erpDb from "./erpDb.js";
7
- const SALT_ROUNDS = 10;
8
- /**
9
- * Ensure a superadmin user exists in the local ERP database.
10
- * For standalone mode (no supervisor auth).
11
- */
12
- export async function ensureLocalSuperAdmin() {
13
- const existing = await erpDb.user.findUnique({
14
- where: { username: SUPER_ADMIN_USERNAME },
15
- });
16
- if (existing) {
17
- // Ensure superadmin has erp_admin permission
18
- await ensureErpAdminPermission(existing.id);
19
- }
20
- else {
21
- const password = randomUUID().slice(0, 8);
22
- const hash = await bcrypt.hash(password, SALT_ROUNDS);
23
- const user = await erpDb.user.create({
24
- data: {
25
- uuid: randomUUID(),
26
- username: SUPER_ADMIN_USERNAME,
27
- passwordHash: hash,
28
- apiKey: randomBytes(32).toString("hex"),
29
- },
30
- });
31
- await ensureErpAdminPermission(user.id);
32
- console.log(`\n ${SUPER_ADMIN_USERNAME} user created. Password: ${password}`);
33
- console.log(` Change it via --reset-password\n`);
34
- }
35
- // Warn if agent users exist without supervisor auth
36
- const agentCount = await erpDb.user.count({ where: { isAgent: true } });
37
- if (agentCount > 0) {
38
- console.warn(`[ERP] Warning: ${agentCount} agent user(s) found but supervisor auth is disabled. ` +
39
- `Agent API key lookups and authentication will not work. ` +
40
- `Start with --supervisor-auth to enable.`);
41
- }
42
- }
43
- /**
44
- * Sync superadmin from supervisor into ERP DB and ensure permissions.
45
- * For supervisor auth mode.
46
- */
47
- export async function ensureSupervisorSuperAdmin() {
48
- const result = await ensureSuperAdmin();
49
- await erpDb.user.upsert({
50
- where: { uuid: result.user.uuid },
51
- create: {
52
- uuid: result.user.uuid,
53
- username: result.user.username,
54
- passwordHash: result.user.passwordHash,
55
- apiKey: result.user.apiKey,
56
- },
57
- update: {
58
- username: result.user.username,
59
- passwordHash: result.user.passwordHash,
60
- apiKey: result.user.apiKey,
61
- },
62
- });
63
- const localSuperAdmin = await erpDb.user.findUnique({
64
- where: { uuid: result.user.uuid },
65
- });
66
- if (localSuperAdmin) {
67
- await ensureErpAdminPermission(localSuperAdmin.id);
68
- }
69
- if (result.created) {
70
- console.log(`[ERP] ${SUPER_ADMIN_USERNAME} user created. Password: ${result.generatedPassword}`);
71
- }
72
- }
73
- /**
74
- * Ensure a user has the erp_admin permission.
75
- */
76
- export async function ensureErpAdminPermission(userId) {
77
- const existing = await erpDb.userPermission.findUnique({
78
- where: { userId_permission: { userId, permission: "erp_admin" } },
79
- });
80
- if (!existing) {
81
- await erpDb.userPermission.create({
82
- data: { userId, permission: "erp_admin" },
83
- });
84
- }
85
- }
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
- //# sourceMappingURL=userService.js.map
File without changes
File without changes