@naisys/erp 3.0.0-beta.9 → 3.0.1
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.
- package/.env.example +5 -0
- package/client-dist/assets/index-CSiMTJfw.css +14 -0
- package/client-dist/assets/index-D5R6NBeW.js +11177 -0
- package/client-dist/assets/rolldown-runtime-B-1-B7_t.js +33 -0
- package/client-dist/assets/vendor-CbiEATh3.js +73851 -0
- package/client-dist/assets/vendor-D8d_HnwE.css +9604 -0
- package/client-dist/favicon-16x16.png +0 -0
- package/client-dist/favicon-32x32.png +0 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +17 -2
- package/dist/{dbConfig.js → database/dbConfig.js} +1 -1
- package/dist/{erpDb.js → database/erpDb.js} +1 -1
- package/dist/erpRoutes.js +115 -0
- package/dist/erpServer.js +92 -162
- package/dist/error-handler.js +3 -0
- package/dist/generated/prisma/internal/class.js +6 -6
- package/dist/generated/prisma/internal/prismaNamespace.js +7 -5
- package/dist/middleware/auth-middleware.js +146 -0
- package/dist/route-helpers.js +2 -2
- package/dist/routes/admin.js +15 -7
- package/dist/routes/audit.js +1 -1
- package/dist/routes/{item-fields.js → items/item-fields.js} +24 -22
- package/dist/routes/{item-instances.js → items/item-instances.js} +42 -24
- package/dist/routes/{items.js → items/items.js} +35 -33
- package/dist/routes/{operation-dependencies.js → operations/operation-dependencies.js} +6 -6
- package/dist/routes/{operation-field-refs.js → operations/operation-field-refs.js} +6 -6
- package/dist/routes/{operation-run-comments.js → operations/operation-run-comments.js} +5 -5
- package/dist/routes/{operation-run-transitions.js → operations/operation-run-transitions.js} +29 -13
- package/dist/routes/{operation-runs.js → operations/operation-runs.js} +48 -10
- package/dist/routes/{operations.js → operations/operations.js} +6 -6
- package/dist/routes/{order-revision-transitions.js → orders/order-revision-transitions.js} +4 -4
- package/dist/routes/{order-revisions.js → orders/order-revisions.js} +6 -6
- package/dist/routes/{order-run-transitions.js → orders/order-run-transitions.js} +11 -5
- package/dist/routes/{order-runs.js → orders/order-runs.js} +7 -5
- package/dist/routes/{orders.js → orders/orders.js} +15 -11
- package/dist/routes/{dispatch.js → production/dispatch.js} +88 -7
- package/dist/routes/{inventory.js → production/inventory.js} +33 -10
- package/dist/routes/{labor-tickets.js → production/labor-tickets.js} +7 -7
- package/dist/routes/{work-centers.js → production/work-centers.js} +29 -29
- package/dist/routes/root.js +1 -1
- package/dist/routes/{step-field-attachments.js → steps/step-field-attachments.js} +8 -8
- package/dist/routes/{step-fields.js → steps/step-fields.js} +6 -6
- package/dist/routes/{step-run-fields.js → steps/step-run-fields.js} +9 -9
- package/dist/routes/{step-run-transitions.js → steps/step-run-transitions.js} +6 -6
- package/dist/routes/{step-runs.js → steps/step-runs.js} +7 -7
- package/dist/routes/{steps.js → steps/steps.js} +5 -5
- package/dist/routes/{auth.js → users/auth.js} +11 -23
- package/dist/routes/{user-permissions.js → users/user-permissions.js} +21 -7
- package/dist/routes/{users.js → users/users.js} +42 -20
- package/dist/services/attachment-service.js +2 -2
- package/dist/services/{item-instance-service.js → inventory/item-instance-service.js} +2 -2
- package/dist/services/{item-service.js → inventory/item-service.js} +2 -2
- package/dist/services/{operation-dependency-service.js → operations/operation-dependency-service.js} +1 -1
- package/dist/services/{operation-run-comment-service.js → operations/operation-run-comment-service.js} +1 -1
- package/dist/services/{operation-run-service.js → operations/operation-run-service.js} +15 -4
- package/dist/services/{operation-service.js → operations/operation-service.js} +2 -2
- package/dist/services/{step-run-service.js → operations/step-run-service.js} +1 -1
- package/dist/services/{step-service.js → operations/step-service.js} +2 -2
- package/dist/services/{order-revision-service.js → orders/order-revision-service.js} +4 -5
- package/dist/services/{order-run-service.js → orders/order-run-service.js} +68 -22
- package/dist/services/{order-service.js → orders/order-service.js} +11 -2
- package/dist/services/{revision-diff-service.js → orders/revision-diff-service.js} +11 -10
- package/dist/services/{field-ref-service.js → production/field-ref-service.js} +1 -1
- package/dist/services/{field-service.js → production/field-service.js} +2 -2
- package/dist/services/{field-value-service.js → production/field-value-service.js} +27 -3
- package/dist/services/production/labor-ticket-backfill.js +67 -0
- package/dist/services/{labor-ticket-service.js → production/labor-ticket-service.js} +21 -15
- package/dist/services/{work-center-service.js → production/work-center-service.js} +2 -2
- package/dist/services/user-service.js +94 -28
- package/dist/version.js +12 -0
- package/npm-shrinkwrap.json +2941 -0
- package/package.json +26 -24
- package/prisma/migrations/20260427010000_hash_user_api_keys/migration.sql +10 -0
- package/prisma/migrations/20260427020000_nullable_user_password_hash/migration.sql +39 -0
- package/prisma/migrations/20260517000000_add_op_run_tokens/migration.sql +2 -0
- package/prisma/schema.prisma +4 -2
- package/client-dist/assets/index-45dVo30p.css +0 -1
- package/client-dist/assets/index-C9uuPHLH.js +0 -168
- package/dist/auth-middleware.js +0 -203
- package/dist/userService.js +0 -118
- /package/bin/{naisys-erp → naisys-erp.js} +0 -0
- /package/dist/{supervisorAuth.js → middleware/supervisorAuth.js} +0 -0
- /package/dist/{audit.js → services/audit.js} +0 -0
package/dist/auth-middleware.js
DELETED
|
@@ -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
|
package/dist/userService.js
DELETED
|
@@ -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
|
|
File without changes
|