@camstack/core 0.1.14 → 0.1.15
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/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +220 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +9 -0
- package/dist/builtins/addon-pages-aggregator/index.js +222 -0
- package/dist/builtins/addon-pages-aggregator/index.js.map +1 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs +9 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +200 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +9 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +202 -0
- package/dist/builtins/addon-widgets-aggregator/index.js.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs +9 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs.map +1 -0
- package/dist/builtins/alerts/alerts.addon.js +443 -0
- package/dist/builtins/alerts/alerts.addon.js.map +1 -0
- package/dist/builtins/alerts/alerts.addon.mjs +9 -0
- package/dist/builtins/alerts/alerts.addon.mjs.map +1 -0
- package/dist/builtins/alerts/index.js +443 -0
- package/dist/builtins/alerts/index.js.map +1 -0
- package/dist/builtins/alerts/index.mjs +8 -0
- package/dist/builtins/alerts/index.mjs.map +1 -0
- package/dist/builtins/console-logging/index.js +242 -0
- package/dist/builtins/console-logging/index.js.map +1 -0
- package/dist/builtins/console-logging/index.mjs +11 -0
- package/dist/builtins/console-logging/index.mjs.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.js +2155 -0
- package/dist/builtins/device-manager/device-manager.addon.js.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs +9 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -0
- package/dist/builtins/device-manager/index.js +2157 -0
- package/dist/builtins/device-manager/index.js.map +1 -0
- package/dist/builtins/device-manager/index.mjs +10 -0
- package/dist/builtins/device-manager/index.mjs.map +1 -0
- package/dist/builtins/hub-forwarder/index.js +297 -0
- package/dist/builtins/hub-forwarder/index.js.map +1 -0
- package/dist/builtins/hub-forwarder/index.mjs +11 -0
- package/dist/builtins/hub-forwarder/index.mjs.map +1 -0
- package/dist/builtins/local-auth/index.js +623 -0
- package/dist/builtins/local-auth/index.js.map +1 -0
- package/dist/builtins/local-auth/index.mjs +8 -0
- package/dist/builtins/local-auth/index.mjs.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.js +623 -0
- package/dist/builtins/local-auth/local-auth.addon.js.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs +9 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -0
- package/dist/builtins/local-backup/index.js +53 -68
- package/dist/builtins/local-backup/index.js.map +1 -1
- package/dist/builtins/local-backup/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +898 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +7 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -0
- package/dist/builtins/snapshot/index.js +504 -0
- package/dist/builtins/snapshot/index.js.map +1 -0
- package/dist/builtins/snapshot/index.mjs +477 -0
- package/dist/builtins/snapshot/index.mjs.map +1 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +16 -166
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +554 -621
- package/dist/builtins/sqlite-storage/index.js.map +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +9 -11
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +368 -130
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/system-config/index.js +189 -0
- package/dist/builtins/system-config/index.js.map +1 -0
- package/dist/builtins/system-config/index.mjs +10 -0
- package/dist/builtins/system-config/index.mjs.map +1 -0
- package/dist/builtins/system-config/system-config.addon.js +187 -0
- package/dist/builtins/system-config/system-config.addon.js.map +1 -0
- package/dist/builtins/system-config/system-config.addon.mjs +9 -0
- package/dist/builtins/system-config/system-config.addon.mjs.map +1 -0
- package/dist/builtins/winston-logging/index.js +185 -65
- package/dist/builtins/winston-logging/index.js.map +1 -1
- package/dist/builtins/winston-logging/index.mjs +2 -1
- package/dist/chunk-2CIYKDRN.mjs +1 -0
- package/dist/chunk-2CIYKDRN.mjs.map +1 -0
- package/dist/chunk-2F76X6NL.mjs +136 -0
- package/dist/chunk-2F76X6NL.mjs.map +1 -0
- package/dist/chunk-2QUFBZ7M.mjs +1 -0
- package/dist/chunk-2QUFBZ7M.mjs.map +1 -0
- package/dist/chunk-3BK2Y7GY.mjs +593 -0
- package/dist/chunk-3BK2Y7GY.mjs.map +1 -0
- package/dist/chunk-4OOHFJHT.mjs +421 -0
- package/dist/chunk-4OOHFJHT.mjs.map +1 -0
- package/dist/chunk-4XHB7IHT.mjs +809 -0
- package/dist/chunk-4XHB7IHT.mjs.map +1 -0
- package/dist/{chunk-2F3XZYRW.mjs → chunk-6M2HSSTQ.mjs} +16 -7
- package/dist/chunk-6M2HSSTQ.mjs.map +1 -0
- package/dist/{chunk-SO4LROOT.mjs → chunk-7FI7SQS7.mjs} +54 -69
- package/dist/chunk-7FI7SQS7.mjs.map +1 -0
- package/dist/chunk-ED57RCQE.mjs +171 -0
- package/dist/chunk-ED57RCQE.mjs.map +1 -0
- package/dist/chunk-FZN56HGQ.mjs +626 -0
- package/dist/chunk-FZN56HGQ.mjs.map +1 -0
- package/dist/chunk-GL4OOB25.mjs +51 -0
- package/dist/chunk-GL4OOB25.mjs.map +1 -0
- package/dist/chunk-KDG2NTDB.mjs +137 -0
- package/dist/chunk-KDG2NTDB.mjs.map +1 -0
- package/dist/chunk-NRBQWBDM.mjs +191 -0
- package/dist/chunk-NRBQWBDM.mjs.map +1 -0
- package/dist/chunk-O4V246GG.mjs +2137 -0
- package/dist/chunk-O4V246GG.mjs.map +1 -0
- package/dist/chunk-QT57H266.mjs +163 -0
- package/dist/chunk-QT57H266.mjs.map +1 -0
- package/dist/chunk-QX4RH25I.mjs +141 -0
- package/dist/chunk-QX4RH25I.mjs.map +1 -0
- package/dist/chunk-TB562PZX.mjs +86 -0
- package/dist/chunk-TB562PZX.mjs.map +1 -0
- package/dist/chunk-TDYPZXK5.mjs +1 -0
- package/dist/chunk-TDYPZXK5.mjs.map +1 -0
- package/dist/chunk-UJI4LN5P.mjs +36 -0
- package/dist/chunk-UJI4LN5P.mjs.map +1 -0
- package/dist/chunk-W6RTHQGP.mjs +1 -0
- package/dist/chunk-W6RTHQGP.mjs.map +1 -0
- package/dist/chunk-ZELBCPDC.mjs +369 -0
- package/dist/chunk-ZELBCPDC.mjs.map +1 -0
- package/dist/index.d.mts +1103 -544
- package/dist/index.d.ts +1103 -544
- package/dist/index.js +7032 -6033
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +568 -2226
- package/dist/index.mjs.map +1 -1
- package/dist/resource-monitor-UZUGPIAU.mjs +9 -0
- package/dist/resource-monitor-UZUGPIAU.mjs.map +1 -0
- package/dist/storage-location-manager-HFNB3PCS.mjs +7 -0
- package/dist/storage-location-manager-HFNB3PCS.mjs.map +1 -0
- package/package.json +123 -2
- package/dist/builtins/local-backup/index.d.mts +0 -42
- package/dist/builtins/local-backup/index.d.ts +0 -42
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +0 -2
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +0 -2
- package/dist/builtins/sqlite-storage/index.d.mts +0 -4
- package/dist/builtins/sqlite-storage/index.d.ts +0 -4
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +0 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +0 -2
- package/dist/builtins/winston-logging/index.d.mts +0 -30
- package/dist/builtins/winston-logging/index.d.ts +0 -30
- package/dist/chunk-2F3XZYRW.mjs.map +0 -1
- package/dist/chunk-LQFPAEQF.mjs +0 -147
- package/dist/chunk-LQFPAEQF.mjs.map +0 -1
- package/dist/chunk-R3DIIBBX.mjs +0 -532
- package/dist/chunk-R3DIIBBX.mjs.map +0 -1
- package/dist/chunk-SMNR44VG.mjs +0 -386
- package/dist/chunk-SMNR44VG.mjs.map +0 -1
- package/dist/chunk-SO4LROOT.mjs.map +0 -1
- package/dist/chunk-SPA4JBKN.mjs +0 -175
- package/dist/chunk-SPA4JBKN.mjs.map +0 -1
- package/dist/dist-3BY63UQ5.mjs +0 -2151
- package/dist/dist-3BY63UQ5.mjs.map +0 -1
- package/dist/filesystem-storage.addon-C42r589X.d.mts +0 -57
- package/dist/filesystem-storage.addon-C42r589X.d.ts +0 -57
- package/dist/sql-schema-CKz78rId.d.mts +0 -97
- package/dist/sql-schema-CKz78rId.d.ts +0 -97
- package/dist/sqlite-settings.addon-KwG-uKMP.d.mts +0 -79
- package/dist/sqlite-settings.addon-KwG-uKMP.d.ts +0 -79
- package/dist/storage-location-manager-KKDQNAKA.mjs +0 -7
- /package/dist/{storage-location-manager-KKDQNAKA.mjs.map → builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map} +0 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
// src/builtins/local-auth/local-auth.addon.ts
|
|
2
|
+
import { BaseAddon, authProviderCapability, userManagementCapability } from "@camstack/types";
|
|
3
|
+
|
|
4
|
+
// src/auth/auth-manager.ts
|
|
5
|
+
import * as jwt from "jsonwebtoken";
|
|
6
|
+
import * as bcrypt from "bcryptjs";
|
|
7
|
+
import * as crypto from "crypto";
|
|
8
|
+
var noopLogger = {
|
|
9
|
+
debug() {
|
|
10
|
+
},
|
|
11
|
+
info() {
|
|
12
|
+
},
|
|
13
|
+
warn() {
|
|
14
|
+
},
|
|
15
|
+
error() {
|
|
16
|
+
},
|
|
17
|
+
child() {
|
|
18
|
+
return noopLogger;
|
|
19
|
+
},
|
|
20
|
+
withTags() {
|
|
21
|
+
return noopLogger;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var AuthManager = class {
|
|
25
|
+
constructor(config, logger = noopLogger) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
const configured = this.config.get("auth.jwtSecret");
|
|
29
|
+
if (configured) {
|
|
30
|
+
this.jwtSecret = configured;
|
|
31
|
+
} else {
|
|
32
|
+
const secret = crypto.randomBytes(32).toString("hex");
|
|
33
|
+
this.config.update("auth", { jwtSecret: secret });
|
|
34
|
+
this.logger.info("Generated JWT secret and saved to config.yaml (auth.jwtSecret)");
|
|
35
|
+
this.jwtSecret = secret;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
config;
|
|
39
|
+
jwtSecret;
|
|
40
|
+
scopedTokenManager = null;
|
|
41
|
+
logger;
|
|
42
|
+
signToken(payload) {
|
|
43
|
+
return jwt.sign({ ...payload }, this.jwtSecret, { expiresIn: "24h" });
|
|
44
|
+
}
|
|
45
|
+
verifyToken(token) {
|
|
46
|
+
return jwt.verify(token, this.jwtSecret);
|
|
47
|
+
}
|
|
48
|
+
async hashPassword(password) {
|
|
49
|
+
return bcrypt.hash(password, 10);
|
|
50
|
+
}
|
|
51
|
+
async comparePassword(password, hash2) {
|
|
52
|
+
return bcrypt.compare(password, hash2);
|
|
53
|
+
}
|
|
54
|
+
generateApiKey() {
|
|
55
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
56
|
+
const hash2 = crypto.createHash("sha256").update(token).digest("hex");
|
|
57
|
+
const prefix = token.slice(0, 8);
|
|
58
|
+
return { token, hash: hash2, prefix };
|
|
59
|
+
}
|
|
60
|
+
validateApiKey(token, storedHash) {
|
|
61
|
+
const hash2 = crypto.createHash("sha256").update(token).digest("hex");
|
|
62
|
+
return hash2 === storedHash;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a service token for agent/worker authentication.
|
|
66
|
+
* Used when forking workers or when agents register.
|
|
67
|
+
*/
|
|
68
|
+
createServiceToken(opts) {
|
|
69
|
+
const payload = {
|
|
70
|
+
userId: opts.agentId,
|
|
71
|
+
username: opts.agentId,
|
|
72
|
+
role: opts.role ?? "agent",
|
|
73
|
+
type: "service",
|
|
74
|
+
agentId: opts.agentId,
|
|
75
|
+
allowedProviders: "*",
|
|
76
|
+
allowedDevices: {}
|
|
77
|
+
};
|
|
78
|
+
const expiresIn = opts.expiresIn ?? "24h";
|
|
79
|
+
return jwt.sign(payload, this.jwtSecret, { expiresIn });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Set the scoped token manager for the auth chain.
|
|
83
|
+
*/
|
|
84
|
+
setScopedTokenManager(manager) {
|
|
85
|
+
this.scopedTokenManager = manager;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Validate a scoped token string.
|
|
89
|
+
* Returns the token record if valid, null otherwise.
|
|
90
|
+
*/
|
|
91
|
+
async validateScopedToken(rawToken) {
|
|
92
|
+
if (!this.scopedTokenManager) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return this.scopedTokenManager.validate(rawToken);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check whether a scoped token grants access to a given addon/route/capability.
|
|
99
|
+
*/
|
|
100
|
+
matchesScopedTokenScope(token, addonId, routePath, capability) {
|
|
101
|
+
if (!this.scopedTokenManager) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return this.scopedTokenManager.matchesScope(token, addonId, routePath, capability);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/auth/user-manager.ts
|
|
109
|
+
import * as crypto2 from "crypto";
|
|
110
|
+
import { UserRecordSchema } from "@camstack/types";
|
|
111
|
+
|
|
112
|
+
// src/auth/parse-record.ts
|
|
113
|
+
function parseRecord(kind, schema, data) {
|
|
114
|
+
try {
|
|
115
|
+
return schema.parse(data);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const topKeys = data && typeof data === "object" && !Array.isArray(data) ? Object.keys(data).join(", ") || "(empty)" : `(non-object: ${typeof data})`;
|
|
118
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
119
|
+
const looksDoubleWrapped = data && typeof data === "object" && !Array.isArray(data) && Object.keys(data).length === 1 && "data" in data;
|
|
120
|
+
const hint = looksDoubleWrapped ? " This shape is the legacy double-wrap from a stale settings-store query path. Restart the server with the latest @camstack/core build." : "";
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Failed to parse ${kind} from settings store. Top-level keys=[${topKeys}].${hint} Underlying: ${detail}`,
|
|
123
|
+
{ cause: err }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/auth/user-manager.ts
|
|
129
|
+
var USERS_COLLECTION = "users";
|
|
130
|
+
function parseUser(data) {
|
|
131
|
+
return parseRecord("user", UserRecordSchema, data);
|
|
132
|
+
}
|
|
133
|
+
var UserManager = class {
|
|
134
|
+
constructor(storageAccess, auth, config) {
|
|
135
|
+
this.storageAccess = storageAccess;
|
|
136
|
+
this.auth = auth;
|
|
137
|
+
this.config = config;
|
|
138
|
+
}
|
|
139
|
+
storageAccess;
|
|
140
|
+
auth;
|
|
141
|
+
config;
|
|
142
|
+
get store() {
|
|
143
|
+
return this.storageAccess.getStore();
|
|
144
|
+
}
|
|
145
|
+
async create(input) {
|
|
146
|
+
const existing = await this.findByUsername(input.username);
|
|
147
|
+
if (existing) throw new Error(`User with username "${input.username}" already exists`);
|
|
148
|
+
const passwordHash = await this.auth.hashPassword(input.password);
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const record = {
|
|
151
|
+
id: crypto2.randomUUID(),
|
|
152
|
+
username: input.username,
|
|
153
|
+
passwordHash,
|
|
154
|
+
role: input.role,
|
|
155
|
+
allowedProviders: input.allowedProviders ?? "*",
|
|
156
|
+
allowedDevices: input.allowedDevices ?? {},
|
|
157
|
+
createdAt: now,
|
|
158
|
+
updatedAt: now
|
|
159
|
+
};
|
|
160
|
+
await this.store.insert.mutate({ collection: USERS_COLLECTION, record: { id: record.id, data: { ...record } } });
|
|
161
|
+
return record;
|
|
162
|
+
}
|
|
163
|
+
async findByUsername(username) {
|
|
164
|
+
const results = await this.store.query.query({ collection: USERS_COLLECTION, filter: { where: { username } } });
|
|
165
|
+
if (results.length === 0) return null;
|
|
166
|
+
return parseUser(results[0].data);
|
|
167
|
+
}
|
|
168
|
+
async findById(id) {
|
|
169
|
+
const results = await this.store.query.query({ collection: USERS_COLLECTION, filter: { where: { id } } });
|
|
170
|
+
if (results.length === 0) return null;
|
|
171
|
+
return parseUser(results[0].data);
|
|
172
|
+
}
|
|
173
|
+
async validateCredentials(username, password) {
|
|
174
|
+
const user = await this.findByUsername(username);
|
|
175
|
+
if (!user) return null;
|
|
176
|
+
const valid = await this.auth.comparePassword(password, user.passwordHash);
|
|
177
|
+
return valid ? user : null;
|
|
178
|
+
}
|
|
179
|
+
async listAll() {
|
|
180
|
+
const results = await this.store.query.query({ collection: USERS_COLLECTION });
|
|
181
|
+
return results.map((r) => {
|
|
182
|
+
const parsed = parseUser(r.data);
|
|
183
|
+
const { passwordHash: _ph, ...rest } = parsed;
|
|
184
|
+
return rest;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async update(id, data) {
|
|
188
|
+
const existing = await this.findById(id);
|
|
189
|
+
if (!existing) throw new Error(`User with id "${id}" not found`);
|
|
190
|
+
await this.store.update.mutate({ collection: USERS_COLLECTION, id, data: { ...existing, ...data, updatedAt: Date.now() } });
|
|
191
|
+
}
|
|
192
|
+
async delete(id) {
|
|
193
|
+
await this.store.delete.mutate({ collection: USERS_COLLECTION, key: id });
|
|
194
|
+
}
|
|
195
|
+
async resetPassword(id, newPassword) {
|
|
196
|
+
const existing = await this.findById(id);
|
|
197
|
+
if (!existing) throw new Error(`User with id "${id}" not found`);
|
|
198
|
+
const passwordHash = await this.auth.hashPassword(newPassword);
|
|
199
|
+
await this.store.update.mutate({ collection: USERS_COLLECTION, id, data: { ...existing, passwordHash, updatedAt: Date.now() } });
|
|
200
|
+
}
|
|
201
|
+
async ensureAdminExists() {
|
|
202
|
+
const adminUsername = this.config.get("auth.adminUsername");
|
|
203
|
+
const adminPassword = this.config.get("auth.adminPassword");
|
|
204
|
+
if (!adminUsername || !adminPassword) return;
|
|
205
|
+
const existing = await this.findByUsername(adminUsername);
|
|
206
|
+
if (existing) {
|
|
207
|
+
const matches = await this.auth.comparePassword(adminPassword, existing.passwordHash);
|
|
208
|
+
if (!matches) {
|
|
209
|
+
await this.resetPassword(existing.id, adminPassword);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
await this.create({ username: adminUsername, password: adminPassword, role: "super_admin", allowedProviders: "*", allowedDevices: {} });
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/auth/api-key-manager.ts
|
|
218
|
+
import * as crypto3 from "crypto";
|
|
219
|
+
import { ApiKeyRecordSchema } from "@camstack/types";
|
|
220
|
+
var API_KEYS_COLLECTION = "api_keys";
|
|
221
|
+
function parseApiKey(data) {
|
|
222
|
+
return parseRecord("api-key", ApiKeyRecordSchema, data);
|
|
223
|
+
}
|
|
224
|
+
var ApiKeyManager = class {
|
|
225
|
+
constructor(storageAccess, auth) {
|
|
226
|
+
this.storageAccess = storageAccess;
|
|
227
|
+
this.auth = auth;
|
|
228
|
+
}
|
|
229
|
+
storageAccess;
|
|
230
|
+
auth;
|
|
231
|
+
get store() {
|
|
232
|
+
return this.storageAccess.getStore();
|
|
233
|
+
}
|
|
234
|
+
async create(input) {
|
|
235
|
+
const { token: rawToken, hash: hash2, prefix } = this.auth.generateApiKey();
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
const record = {
|
|
238
|
+
id: crypto3.randomUUID(),
|
|
239
|
+
label: input.label,
|
|
240
|
+
role: input.role,
|
|
241
|
+
allowedProviders: input.allowedProviders ?? "*",
|
|
242
|
+
allowedDevices: input.allowedDevices ?? {},
|
|
243
|
+
tokenHash: hash2,
|
|
244
|
+
tokenPrefix: prefix,
|
|
245
|
+
createdAt: now
|
|
246
|
+
};
|
|
247
|
+
await this.store.insert.mutate({ collection: API_KEYS_COLLECTION, record: { id: record.id, data: { ...record } } });
|
|
248
|
+
return { record, token: rawToken };
|
|
249
|
+
}
|
|
250
|
+
async validateToken(token) {
|
|
251
|
+
const allKeys = await this.store.query.query({ collection: API_KEYS_COLLECTION });
|
|
252
|
+
for (const entry of allKeys) {
|
|
253
|
+
const record = parseApiKey(entry.data);
|
|
254
|
+
if (this.auth.validateApiKey(token, record.tokenHash)) {
|
|
255
|
+
const updatedData = { ...record, lastUsedAt: Date.now() };
|
|
256
|
+
await this.store.update.mutate({ collection: API_KEYS_COLLECTION, id: record.id, data: { ...updatedData } });
|
|
257
|
+
return updatedData;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
async listAll() {
|
|
263
|
+
const results = await this.store.query.query({ collection: API_KEYS_COLLECTION });
|
|
264
|
+
return results.map((r) => {
|
|
265
|
+
const parsed = parseApiKey(r.data);
|
|
266
|
+
const { tokenHash: _th, ...rest } = parsed;
|
|
267
|
+
return rest;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async revoke(id) {
|
|
271
|
+
await this.store.delete.mutate({ collection: API_KEYS_COLLECTION, key: id });
|
|
272
|
+
}
|
|
273
|
+
async findById(id) {
|
|
274
|
+
const results = await this.store.query.query({ collection: API_KEYS_COLLECTION, filter: { where: { id } } });
|
|
275
|
+
if (results.length === 0) return null;
|
|
276
|
+
return parseApiKey(results[0].data);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/auth/scoped-token-manager.ts
|
|
281
|
+
import * as crypto4 from "crypto";
|
|
282
|
+
import { ScopedTokenSchema } from "@camstack/types";
|
|
283
|
+
var TOKENS_COLLECTION = "scoped_tokens";
|
|
284
|
+
var TOKEN_PREFIX = "cst_";
|
|
285
|
+
function parseToken(data) {
|
|
286
|
+
return parseRecord("scoped-token", ScopedTokenSchema, data);
|
|
287
|
+
}
|
|
288
|
+
var ScopedTokenManager = class {
|
|
289
|
+
constructor(store) {
|
|
290
|
+
this.store = store;
|
|
291
|
+
}
|
|
292
|
+
store;
|
|
293
|
+
async create(userId, name, scopes, expiresAt) {
|
|
294
|
+
const rawHex = crypto4.randomBytes(32).toString("hex");
|
|
295
|
+
const rawToken = `${TOKEN_PREFIX}${rawHex}`;
|
|
296
|
+
const tokenHash = crypto4.createHash("sha256").update(rawToken).digest("hex");
|
|
297
|
+
const tokenPrefix = rawToken.slice(0, 12);
|
|
298
|
+
const record = {
|
|
299
|
+
id: crypto4.randomUUID(),
|
|
300
|
+
userId,
|
|
301
|
+
name,
|
|
302
|
+
tokenHash,
|
|
303
|
+
tokenPrefix,
|
|
304
|
+
scopes: scopes.map((s) => ({ ...s })),
|
|
305
|
+
expiresAt,
|
|
306
|
+
lastUsedAt: void 0,
|
|
307
|
+
createdAt: Date.now()
|
|
308
|
+
};
|
|
309
|
+
await this.store.insert.mutate({ collection: TOKENS_COLLECTION, record: { id: record.id, data: { ...record } } });
|
|
310
|
+
return { token: rawToken, record };
|
|
311
|
+
}
|
|
312
|
+
async validate(rawToken) {
|
|
313
|
+
if (!rawToken.startsWith(TOKEN_PREFIX)) return null;
|
|
314
|
+
const tokenHash = crypto4.createHash("sha256").update(rawToken).digest("hex");
|
|
315
|
+
const results = await this.store.query.query({ collection: TOKENS_COLLECTION, filter: { where: { tokenHash } } });
|
|
316
|
+
if (results.length === 0) return null;
|
|
317
|
+
const record = parseToken(results[0].data);
|
|
318
|
+
if (record.expiresAt !== void 0 && record.expiresAt !== null && Date.now() > record.expiresAt) return null;
|
|
319
|
+
this.updateLastUsed(record.id).catch(() => {
|
|
320
|
+
});
|
|
321
|
+
return record;
|
|
322
|
+
}
|
|
323
|
+
matchesScope(token, addonId, routePath, capability) {
|
|
324
|
+
for (const scope of token.scopes) {
|
|
325
|
+
switch (scope.type) {
|
|
326
|
+
case "addon":
|
|
327
|
+
if (addonId && scope.target === addonId) return true;
|
|
328
|
+
break;
|
|
329
|
+
case "route-prefix":
|
|
330
|
+
if (routePath && routePath.startsWith(scope.target)) return true;
|
|
331
|
+
break;
|
|
332
|
+
case "capability":
|
|
333
|
+
if (capability && scope.target === capability) return true;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
async revoke(tokenId) {
|
|
340
|
+
await this.store.delete.mutate({ collection: TOKENS_COLLECTION, key: tokenId });
|
|
341
|
+
}
|
|
342
|
+
async listForUser(userId) {
|
|
343
|
+
const results = await this.store.query.query({ collection: TOKENS_COLLECTION, filter: { where: { userId } } });
|
|
344
|
+
return results.map((r) => parseToken(r.data));
|
|
345
|
+
}
|
|
346
|
+
async updateLastUsed(tokenId) {
|
|
347
|
+
const results = await this.store.query.query({ collection: TOKENS_COLLECTION, filter: { where: { id: tokenId } } });
|
|
348
|
+
if (results.length === 0) return;
|
|
349
|
+
const existing = parseToken(results[0].data);
|
|
350
|
+
await this.store.update.mutate({ collection: TOKENS_COLLECTION, id: tokenId, data: { ...existing, lastUsedAt: Date.now() } });
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/builtins/local-auth/auth-schema.ts
|
|
355
|
+
var USERS_COLLECTION2 = "users";
|
|
356
|
+
var API_KEYS_COLLECTION2 = "api_keys";
|
|
357
|
+
var SCOPED_TOKENS_COLLECTION = "scoped_tokens";
|
|
358
|
+
var USERS_COLUMNS = [
|
|
359
|
+
{ name: "id", type: "TEXT", primaryKey: true, notNull: true },
|
|
360
|
+
{ name: "username", type: "TEXT", notNull: true, unique: true },
|
|
361
|
+
{ name: "passwordHash", type: "TEXT", notNull: true },
|
|
362
|
+
{ name: "role", type: "TEXT", notNull: true },
|
|
363
|
+
{ name: "allowedProviders", type: "JSON" },
|
|
364
|
+
{ name: "allowedDevices", type: "JSON" },
|
|
365
|
+
{ name: "createdAt", type: "INTEGER", notNull: true },
|
|
366
|
+
{ name: "updatedAt", type: "INTEGER", notNull: true }
|
|
367
|
+
];
|
|
368
|
+
var API_KEYS_COLUMNS = [
|
|
369
|
+
{ name: "id", type: "TEXT", primaryKey: true, notNull: true },
|
|
370
|
+
{ name: "label", type: "TEXT", notNull: true },
|
|
371
|
+
{ name: "role", type: "TEXT", notNull: true },
|
|
372
|
+
{ name: "tokenHash", type: "TEXT", notNull: true, unique: true },
|
|
373
|
+
{ name: "tokenPrefix", type: "TEXT", notNull: true },
|
|
374
|
+
{ name: "allowedProviders", type: "JSON" },
|
|
375
|
+
{ name: "allowedDevices", type: "JSON" },
|
|
376
|
+
{ name: "createdAt", type: "INTEGER", notNull: true },
|
|
377
|
+
{ name: "lastUsedAt", type: "INTEGER" }
|
|
378
|
+
];
|
|
379
|
+
var SCOPED_TOKENS_COLUMNS = [
|
|
380
|
+
{ name: "id", type: "TEXT", primaryKey: true, notNull: true },
|
|
381
|
+
{ name: "userId", type: "TEXT", notNull: true },
|
|
382
|
+
{ name: "name", type: "TEXT", notNull: true },
|
|
383
|
+
{ name: "tokenHash", type: "TEXT", notNull: true, unique: true },
|
|
384
|
+
{ name: "tokenPrefix", type: "TEXT", notNull: true },
|
|
385
|
+
{ name: "scopes", type: "JSON" },
|
|
386
|
+
{ name: "expiresAt", type: "INTEGER" },
|
|
387
|
+
{ name: "lastUsedAt", type: "INTEGER" },
|
|
388
|
+
{ name: "createdAt", type: "INTEGER", notNull: true }
|
|
389
|
+
];
|
|
390
|
+
async function declareAuthSchema(store) {
|
|
391
|
+
await store.declareCollection.mutate({
|
|
392
|
+
collection: USERS_COLLECTION2,
|
|
393
|
+
columns: USERS_COLUMNS
|
|
394
|
+
});
|
|
395
|
+
await store.declareCollection.mutate({
|
|
396
|
+
collection: API_KEYS_COLLECTION2,
|
|
397
|
+
columns: API_KEYS_COLUMNS,
|
|
398
|
+
indexes: [
|
|
399
|
+
{ name: "idx_api_keys_token_prefix", columns: ["tokenPrefix"] }
|
|
400
|
+
]
|
|
401
|
+
});
|
|
402
|
+
await store.declareCollection.mutate({
|
|
403
|
+
collection: SCOPED_TOKENS_COLLECTION,
|
|
404
|
+
columns: SCOPED_TOKENS_COLUMNS,
|
|
405
|
+
indexes: [
|
|
406
|
+
{ name: "idx_scoped_tokens_user_id", columns: ["userId"] }
|
|
407
|
+
]
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/builtins/local-auth/local-auth.addon.ts
|
|
412
|
+
function toAuthResult(user) {
|
|
413
|
+
return {
|
|
414
|
+
userId: user.id,
|
|
415
|
+
username: user.username,
|
|
416
|
+
displayName: user.username,
|
|
417
|
+
roles: [user.role]
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
var LocalAuthAddon = class extends BaseAddon {
|
|
421
|
+
authManager = null;
|
|
422
|
+
userManager = null;
|
|
423
|
+
apiKeyManager = null;
|
|
424
|
+
scopedTokenManager = null;
|
|
425
|
+
constructor() {
|
|
426
|
+
super({ jwtSecret: "", adminUsername: "", adminPassword: "" });
|
|
427
|
+
}
|
|
428
|
+
async onInitialize() {
|
|
429
|
+
const authSection = await this.ctx.settings?.getSection("auth") ?? {};
|
|
430
|
+
const resolvedJwtSecret = typeof authSection["jwtSecret"] === "string" ? authSection["jwtSecret"] : this.config.jwtSecret ?? "";
|
|
431
|
+
const resolvedAdminUser = typeof authSection["adminUsername"] === "string" && authSection["adminUsername"] ? authSection["adminUsername"] : this.config.adminUsername ?? "";
|
|
432
|
+
const resolvedAdminPass = typeof authSection["adminPassword"] === "string" && authSection["adminPassword"] ? authSection["adminPassword"] : this.config.adminPassword ?? "";
|
|
433
|
+
const reader = {
|
|
434
|
+
get(path) {
|
|
435
|
+
if (path === "auth.jwtSecret") return resolvedJwtSecret;
|
|
436
|
+
if (path === "auth.adminUsername") return resolvedAdminUser;
|
|
437
|
+
if (path === "auth.adminPassword") return resolvedAdminPass;
|
|
438
|
+
return void 0;
|
|
439
|
+
},
|
|
440
|
+
update: (_section, data) => {
|
|
441
|
+
if (typeof data["jwtSecret"] === "string") {
|
|
442
|
+
void this.ctx.settings?.setSection("auth", { jwtSecret: data["jwtSecret"] });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
this.authManager = new AuthManager(reader, this.ctx.logger);
|
|
447
|
+
const store = this.ctx.api?.settingsStore;
|
|
448
|
+
if (store) {
|
|
449
|
+
await declareAuthSchema(store);
|
|
450
|
+
const storageAccess = {
|
|
451
|
+
getStore() {
|
|
452
|
+
return store;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
this.userManager = new UserManager(storageAccess, this.authManager, reader);
|
|
456
|
+
this.apiKeyManager = new ApiKeyManager(storageAccess, this.authManager);
|
|
457
|
+
this.scopedTokenManager = new ScopedTokenManager(store);
|
|
458
|
+
try {
|
|
459
|
+
await this.userManager.ensureAdminExists();
|
|
460
|
+
} catch (err) {
|
|
461
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
462
|
+
throw new Error(
|
|
463
|
+
`local-auth bootstrap failed: ensureAdminExists threw before \`user-management\` could be registered. Most likely a \`users\` collection schema mismatch in the settings-store. Underlying: ${detail}`,
|
|
464
|
+
{ cause: err }
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
throw new Error(
|
|
469
|
+
"local-auth: settings-store API not available \u2014 refusing to register `user-management` cap. Check that `sqlite-storage` addon initialized before `local-auth`."
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
const authProvider = {
|
|
473
|
+
validateCredentials: async (input) => {
|
|
474
|
+
if (!this.userManager) return null;
|
|
475
|
+
const user = await this.userManager.validateCredentials(input.username, input.password);
|
|
476
|
+
return user ? toAuthResult(user) : null;
|
|
477
|
+
},
|
|
478
|
+
getLoginUrl: async () => {
|
|
479
|
+
throw new Error("local-auth: URL-based login not supported");
|
|
480
|
+
},
|
|
481
|
+
handleCallback: async () => {
|
|
482
|
+
throw new Error("local-auth: URL-based login not supported");
|
|
483
|
+
},
|
|
484
|
+
validateToken: async (input) => {
|
|
485
|
+
if (!this.authManager || !this.userManager) return null;
|
|
486
|
+
try {
|
|
487
|
+
const payload = this.authManager.verifyToken(input.token);
|
|
488
|
+
const userId = payload.userId;
|
|
489
|
+
if (!userId) return null;
|
|
490
|
+
const user = await this.userManager.findById(userId);
|
|
491
|
+
return user ? toAuthResult(user) : null;
|
|
492
|
+
} catch {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
const userMgmt = {
|
|
498
|
+
listUsers: async () => {
|
|
499
|
+
if (!this.userManager) return [];
|
|
500
|
+
return this.userManager.listAll();
|
|
501
|
+
},
|
|
502
|
+
createUser: async (input) => {
|
|
503
|
+
if (!this.userManager) throw new Error("User management not available");
|
|
504
|
+
const record = await this.userManager.create(input);
|
|
505
|
+
const { passwordHash: _, ...summary } = record;
|
|
506
|
+
return summary;
|
|
507
|
+
},
|
|
508
|
+
updateUser: async (input) => {
|
|
509
|
+
if (!this.userManager) throw new Error("User management not available");
|
|
510
|
+
const { id, ...data } = input;
|
|
511
|
+
await this.userManager.update(id, data);
|
|
512
|
+
return { success: true };
|
|
513
|
+
},
|
|
514
|
+
deleteUser: async (input) => {
|
|
515
|
+
if (!this.userManager) throw new Error("User management not available");
|
|
516
|
+
await this.userManager.delete(input.id);
|
|
517
|
+
return { success: true };
|
|
518
|
+
},
|
|
519
|
+
resetPassword: async (input) => {
|
|
520
|
+
if (!this.userManager) throw new Error("User management not available");
|
|
521
|
+
await this.userManager.resetPassword(input.id, input.newPassword);
|
|
522
|
+
return { success: true };
|
|
523
|
+
},
|
|
524
|
+
validateCredentials: async (input) => {
|
|
525
|
+
if (!this.userManager) return null;
|
|
526
|
+
const user = await this.userManager.validateCredentials(input.username, input.password);
|
|
527
|
+
return user ?? null;
|
|
528
|
+
},
|
|
529
|
+
listApiKeys: async () => {
|
|
530
|
+
if (!this.apiKeyManager) return [];
|
|
531
|
+
return this.apiKeyManager.listAll();
|
|
532
|
+
},
|
|
533
|
+
createApiKey: async (input) => {
|
|
534
|
+
if (!this.apiKeyManager) throw new Error("API key management not available");
|
|
535
|
+
const { record, token } = await this.apiKeyManager.create(input);
|
|
536
|
+
const { tokenHash: _, ...summary } = record;
|
|
537
|
+
return { token, record: summary };
|
|
538
|
+
},
|
|
539
|
+
revokeApiKey: async (input) => {
|
|
540
|
+
if (!this.apiKeyManager) throw new Error("API key management not available");
|
|
541
|
+
await this.apiKeyManager.revoke(input.id);
|
|
542
|
+
return { success: true };
|
|
543
|
+
},
|
|
544
|
+
validateApiKey: async (input) => {
|
|
545
|
+
if (!this.apiKeyManager) return null;
|
|
546
|
+
const record = await this.apiKeyManager.validateToken(input.token);
|
|
547
|
+
if (!record) return null;
|
|
548
|
+
const { tokenHash: _, ...summary } = record;
|
|
549
|
+
return summary;
|
|
550
|
+
},
|
|
551
|
+
createScopedToken: async (input) => {
|
|
552
|
+
if (!this.scopedTokenManager) throw new Error("Scoped token management not available");
|
|
553
|
+
const { token, record } = await this.scopedTokenManager.create("system", input.name, input.scopes, input.expiresAt);
|
|
554
|
+
return { token, record };
|
|
555
|
+
},
|
|
556
|
+
revokeScopedToken: async (input) => {
|
|
557
|
+
if (!this.scopedTokenManager) throw new Error("Scoped token management not available");
|
|
558
|
+
await this.scopedTokenManager.revoke(input.id);
|
|
559
|
+
return { success: true };
|
|
560
|
+
},
|
|
561
|
+
validateScopedToken: async (input) => {
|
|
562
|
+
if (!this.scopedTokenManager) return null;
|
|
563
|
+
return this.scopedTokenManager.validate(input.token);
|
|
564
|
+
},
|
|
565
|
+
listScopedTokens: async (input) => {
|
|
566
|
+
if (!this.scopedTokenManager) return [];
|
|
567
|
+
return this.scopedTokenManager.listForUser(input.userId);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
this.ctx.logger.info("registered auth-provider + user-management capabilities");
|
|
571
|
+
return [
|
|
572
|
+
{ capability: authProviderCapability, provider: authProvider },
|
|
573
|
+
{ capability: userManagementCapability, provider: userMgmt }
|
|
574
|
+
];
|
|
575
|
+
}
|
|
576
|
+
async onShutdown() {
|
|
577
|
+
this.authManager = null;
|
|
578
|
+
this.userManager = null;
|
|
579
|
+
this.apiKeyManager = null;
|
|
580
|
+
this.scopedTokenManager = null;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
var local_auth_addon_default = LocalAuthAddon;
|
|
584
|
+
|
|
585
|
+
export {
|
|
586
|
+
AuthManager,
|
|
587
|
+
UserManager,
|
|
588
|
+
ApiKeyManager,
|
|
589
|
+
ScopedTokenManager,
|
|
590
|
+
LocalAuthAddon,
|
|
591
|
+
local_auth_addon_default
|
|
592
|
+
};
|
|
593
|
+
//# sourceMappingURL=chunk-3BK2Y7GY.mjs.map
|