@inetafrica/open-claudia 2.2.0 → 2.2.2
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/core/actions.js +22 -0
- package/core/handlers.js +54 -13
- package/core/router.js +42 -2
- package/package.json +1 -1
- package/vault.js +3 -1
package/core/actions.js
CHANGED
|
@@ -108,13 +108,35 @@ async function handleAction(envelope) {
|
|
|
108
108
|
vault.lock();
|
|
109
109
|
return send("Vault locked.");
|
|
110
110
|
}
|
|
111
|
+
if (action === "cancel") {
|
|
112
|
+
if (!state.pendingVaultUnlock) return send("Nothing to cancel.");
|
|
113
|
+
state.pendingVaultUnlock = false;
|
|
114
|
+
state.pendingVaultAction = null;
|
|
115
|
+
state.pendingVaultUnlockAt = 0;
|
|
116
|
+
state.pendingVaultUnlockAttempts = 0;
|
|
117
|
+
return send("Cancelled.");
|
|
118
|
+
}
|
|
119
|
+
if (action === "create") {
|
|
120
|
+
if (!isChatOwner(envelope.channelId)) return send("Owner only.");
|
|
121
|
+
if (vault.exists()) return send("Vault already exists.");
|
|
122
|
+
state.pendingVaultUnlock = true;
|
|
123
|
+
state.pendingVaultUnlockAt = Date.now();
|
|
124
|
+
state.pendingVaultUnlockAttempts = 3;
|
|
125
|
+
state.pendingVaultAction = { type: "create" };
|
|
126
|
+
return send("Pick a vault password and send it next.\n(Message will be deleted. Send /vault cancel to abort.)", {
|
|
127
|
+
keyboard: { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
111
130
|
if (action === "hint") {
|
|
112
131
|
return send([
|
|
113
132
|
"Vault usage:",
|
|
133
|
+
" /vault create — set up the vault (owner, first time)",
|
|
114
134
|
" /vault set <name> <value> — save a credential (your message is deleted)",
|
|
135
|
+
" /vault get <name> — reveal a credential",
|
|
115
136
|
" /vault remove <name> — delete a credential",
|
|
116
137
|
" /vault — list (unlocks if needed)",
|
|
117
138
|
" /vault lock — lock now",
|
|
139
|
+
" /vault cancel — abort a pending password prompt",
|
|
118
140
|
].join("\n"));
|
|
119
141
|
}
|
|
120
142
|
return;
|
package/core/handlers.js
CHANGED
|
@@ -877,27 +877,41 @@ register({
|
|
|
877
877
|
|
|
878
878
|
// ── Vault ──────────────────────────────────────────────────────────
|
|
879
879
|
|
|
880
|
+
const PENDING_VAULT_TTL_MS = 90 * 1000;
|
|
881
|
+
const PASSWORD_PROMPT_KB = { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] };
|
|
882
|
+
|
|
883
|
+
function armVaultPrompt(state, action) {
|
|
884
|
+
state.pendingVaultUnlock = true;
|
|
885
|
+
state.pendingVaultUnlockAt = Date.now();
|
|
886
|
+
state.pendingVaultUnlockAttempts = 3;
|
|
887
|
+
state.pendingVaultAction = action;
|
|
888
|
+
}
|
|
889
|
+
|
|
880
890
|
register({
|
|
881
|
-
name: "vault", description: "Manage credentials (password required)", args: "[set|remove|lock] ...",
|
|
891
|
+
name: "vault", description: "Manage credentials (password required)", args: "[create|get|set|remove|lock|cancel] ...",
|
|
882
892
|
handler: async (env, { tail }) => {
|
|
883
893
|
if (!authorized(env)) return;
|
|
884
894
|
const state = currentState();
|
|
885
895
|
|
|
886
896
|
if (!tail) {
|
|
887
|
-
if (!vault.exists())
|
|
897
|
+
if (!vault.exists()) {
|
|
898
|
+
if (!ownerEnv(env)) return send("No vault. Ask the owner to run /vault create.");
|
|
899
|
+
return send("No vault yet. Use /vault create to set one up.", {
|
|
900
|
+
keyboard: { inline_keyboard: [[{ text: "Create vault", callback_data: "vault:create" }]] },
|
|
901
|
+
});
|
|
902
|
+
}
|
|
888
903
|
if (vault.isUnlocked()) {
|
|
889
904
|
const entries = vault.list();
|
|
890
905
|
const keys = Object.keys(entries);
|
|
891
906
|
const kb = { inline_keyboard: [[
|
|
892
907
|
{ text: "Lock now", callback_data: "vault:lock" },
|
|
893
|
-
{ text: "How to set/remove", callback_data: "vault:hint" },
|
|
908
|
+
{ text: "How to set/get/remove", callback_data: "vault:hint" },
|
|
894
909
|
]] };
|
|
895
910
|
if (keys.length === 0) await send("Vault is unlocked but empty.\n\nUse /vault set <name> <value>", { keyboard: kb });
|
|
896
911
|
else await send("Vault (unlocked):\n\n" + keys.map((k) => `${k}: ${entries[k]}`).join("\n") + "\n\nLocks automatically in 5 min.", { keyboard: kb });
|
|
897
912
|
} else {
|
|
898
|
-
state
|
|
899
|
-
|
|
900
|
-
await send("Vault is locked. Send your vault password.\n(Message will be deleted after reading)");
|
|
913
|
+
armVaultPrompt(state, { type: "list" });
|
|
914
|
+
await send("Vault is locked. Send your vault password.\n(Message will be deleted after reading. Times out in 90s.)", { keyboard: PASSWORD_PROMPT_KB });
|
|
901
915
|
}
|
|
902
916
|
return;
|
|
903
917
|
}
|
|
@@ -907,6 +921,22 @@ register({
|
|
|
907
921
|
return send("Vault locked.");
|
|
908
922
|
}
|
|
909
923
|
|
|
924
|
+
if (tail === "cancel") {
|
|
925
|
+
if (!state.pendingVaultUnlock) return send("Nothing to cancel.");
|
|
926
|
+
state.pendingVaultUnlock = false;
|
|
927
|
+
state.pendingVaultAction = null;
|
|
928
|
+
state.pendingVaultUnlockAt = 0;
|
|
929
|
+
state.pendingVaultUnlockAttempts = 0;
|
|
930
|
+
return send("Cancelled.");
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (tail === "create") {
|
|
934
|
+
if (!ownerEnv(env)) return send("Owner only.");
|
|
935
|
+
if (vault.exists()) return send("Vault already exists. Use /vault to unlock, or delete the file first.");
|
|
936
|
+
armVaultPrompt(state, { type: "create" });
|
|
937
|
+
return send("Pick a vault password and send it next.\n(Message will be deleted. Use the button or send /vault cancel to abort.)", { keyboard: PASSWORD_PROMPT_KB });
|
|
938
|
+
}
|
|
939
|
+
|
|
910
940
|
const setMatch = tail.match(/^set\s+(\S+)\s+(.+)$/);
|
|
911
941
|
if (setMatch) {
|
|
912
942
|
await deleteMessage(env.messageId);
|
|
@@ -914,9 +944,19 @@ register({
|
|
|
914
944
|
vault.set(setMatch[1], setMatch[2].trim());
|
|
915
945
|
return send(`Saved: ${setMatch[1]}`);
|
|
916
946
|
}
|
|
917
|
-
state.
|
|
918
|
-
|
|
919
|
-
|
|
947
|
+
armVaultPrompt(state, { type: "set", key: setMatch[1], value: setMatch[2].trim() });
|
|
948
|
+
return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const getMatch = tail.match(/^get\s+(\S+)$/);
|
|
952
|
+
if (getMatch) {
|
|
953
|
+
if (vault.isUnlocked()) {
|
|
954
|
+
const value = vault.get(getMatch[1]);
|
|
955
|
+
if (value === null) return send(`No such key: ${getMatch[1]}`);
|
|
956
|
+
return send(`${getMatch[1]}: ${value}\n\n(Visible to anyone who reads this chat — delete the message when done.)`);
|
|
957
|
+
}
|
|
958
|
+
armVaultPrompt(state, { type: "get", key: getMatch[1] });
|
|
959
|
+
return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
|
|
920
960
|
}
|
|
921
961
|
|
|
922
962
|
const removeMatch = tail.match(/^remove\s+(\S+)$/);
|
|
@@ -925,15 +965,16 @@ register({
|
|
|
925
965
|
vault.remove(removeMatch[1]);
|
|
926
966
|
return send(`Removed: ${removeMatch[1]}`);
|
|
927
967
|
}
|
|
928
|
-
state
|
|
929
|
-
|
|
930
|
-
return send("Vault locked. Send password to unlock.");
|
|
968
|
+
armVaultPrompt(state, { type: "remove", key: removeMatch[1] });
|
|
969
|
+
return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
|
|
931
970
|
}
|
|
932
971
|
|
|
933
|
-
send("Usage: /vault | /vault set <name> <value> | /vault remove <name> | /vault lock");
|
|
972
|
+
send("Usage: /vault | /vault create | /vault set <name> <value> | /vault get <name> | /vault remove <name> | /vault lock | /vault cancel");
|
|
934
973
|
},
|
|
935
974
|
});
|
|
936
975
|
|
|
976
|
+
module.exports.PENDING_VAULT_TTL_MS = PENDING_VAULT_TTL_MS;
|
|
977
|
+
|
|
937
978
|
// ── Cron ───────────────────────────────────────────────────────────
|
|
938
979
|
|
|
939
980
|
register({
|
package/core/router.js
CHANGED
|
@@ -240,18 +240,54 @@ async function handleText(envelope) {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
if (state.pendingVaultUnlock) {
|
|
243
|
+
const { PENDING_VAULT_TTL_MS } = require("./handlers");
|
|
244
|
+
const armedAt = state.pendingVaultUnlockAt || 0;
|
|
245
|
+
if (PENDING_VAULT_TTL_MS && armedAt && Date.now() - armedAt > PENDING_VAULT_TTL_MS) {
|
|
246
|
+
state.pendingVaultUnlock = false;
|
|
247
|
+
state.pendingVaultAction = null;
|
|
248
|
+
state.pendingVaultUnlockAt = 0;
|
|
249
|
+
state.pendingVaultUnlockAttempts = 0;
|
|
250
|
+
await send("Vault prompt timed out. Re-run the command.");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
243
253
|
const password = envelope.text;
|
|
244
254
|
await deleteMessage(envelope.messageId);
|
|
255
|
+
const action = state.pendingVaultAction;
|
|
256
|
+
|
|
257
|
+
if (action && action.type === "create") {
|
|
258
|
+
state.pendingVaultUnlock = false;
|
|
259
|
+
state.pendingVaultAction = null;
|
|
260
|
+
state.pendingVaultUnlockAt = 0;
|
|
261
|
+
state.pendingVaultUnlockAttempts = 0;
|
|
262
|
+
if (vault.exists()) { await send("Vault was created in another flow — re-run /vault."); return; }
|
|
263
|
+
vault.create(password);
|
|
264
|
+
await send("Vault created and unlocked.\n\nUse /vault set <name> <value>");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
245
268
|
const ok = vault.unlock(password);
|
|
246
269
|
if (!ok) {
|
|
270
|
+
const left = (state.pendingVaultUnlockAttempts || 1) - 1;
|
|
271
|
+
if (left > 0) {
|
|
272
|
+
state.pendingVaultUnlockAttempts = left;
|
|
273
|
+
state.pendingVaultUnlockAt = Date.now();
|
|
274
|
+
await send(`Wrong password. ${left} attempt${left === 1 ? "" : "s"} left.\nSend the password again or /vault cancel.`, {
|
|
275
|
+
keyboard: { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] },
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
247
279
|
state.pendingVaultUnlock = false;
|
|
248
280
|
state.pendingVaultAction = null;
|
|
249
|
-
|
|
281
|
+
state.pendingVaultUnlockAt = 0;
|
|
282
|
+
state.pendingVaultUnlockAttempts = 0;
|
|
283
|
+
await send("Too many wrong attempts. Re-run /vault to try again.");
|
|
250
284
|
return;
|
|
251
285
|
}
|
|
252
|
-
|
|
286
|
+
|
|
253
287
|
state.pendingVaultUnlock = false;
|
|
254
288
|
state.pendingVaultAction = null;
|
|
289
|
+
state.pendingVaultUnlockAt = 0;
|
|
290
|
+
state.pendingVaultUnlockAttempts = 0;
|
|
255
291
|
if (action.type === "list") {
|
|
256
292
|
const entries = vault.list();
|
|
257
293
|
const keys = Object.keys(entries);
|
|
@@ -263,6 +299,10 @@ async function handleText(envelope) {
|
|
|
263
299
|
} else if (action.type === "remove") {
|
|
264
300
|
vault.remove(action.key);
|
|
265
301
|
await send(`Removed: ${action.key}`);
|
|
302
|
+
} else if (action.type === "get") {
|
|
303
|
+
const value = vault.get(action.key);
|
|
304
|
+
if (value === null) await send(`No such key: ${action.key}`);
|
|
305
|
+
else await send(`${action.key}: ${value}\n\n(Visible to anyone who reads this chat — delete the message when done.)`);
|
|
266
306
|
}
|
|
267
307
|
return;
|
|
268
308
|
}
|
package/package.json
CHANGED
package/vault.js
CHANGED
|
@@ -23,7 +23,9 @@ class Vault {
|
|
|
23
23
|
// Create a new vault with a password
|
|
24
24
|
create(password) {
|
|
25
25
|
this.data = {};
|
|
26
|
+
this._password = password;
|
|
26
27
|
this._save(password);
|
|
28
|
+
this._resetLockTimer();
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
// Derive key from password + salt
|
|
@@ -125,7 +127,7 @@ class Vault {
|
|
|
125
127
|
const entries = {};
|
|
126
128
|
for (const [k, v] of Object.entries(this.data)) {
|
|
127
129
|
const val = String(v);
|
|
128
|
-
entries[k] = val.length
|
|
130
|
+
entries[k] = val.length >= 4 ? "****" + val.slice(-4) : "****";
|
|
129
131
|
}
|
|
130
132
|
return entries;
|
|
131
133
|
}
|