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