run402 1.30.0 → 1.32.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.
- package/core-dist/wallet-auth.js +62 -0
- package/core-dist/wallet.js +25 -0
- package/lib/projects.mjs +1 -1
- package/lib/sender-domain.mjs +35 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet auth helper — generates EIP-191 signature headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
7
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
8
|
+
import { readWallet } from "./wallet.js";
|
|
9
|
+
/**
|
|
10
|
+
* EIP-191 personal_sign: sign a message with the wallet's private key.
|
|
11
|
+
*/
|
|
12
|
+
function personalSign(privateKeyHex, address, message) {
|
|
13
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
14
|
+
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
|
|
15
|
+
const prefixed = new Uint8Array(prefix.length + msgBytes.length);
|
|
16
|
+
prefixed.set(prefix);
|
|
17
|
+
prefixed.set(msgBytes, prefix.length);
|
|
18
|
+
const hash = keccak_256(prefixed);
|
|
19
|
+
const pkHex = privateKeyHex.startsWith("0x")
|
|
20
|
+
? privateKeyHex.slice(2)
|
|
21
|
+
: privateKeyHex;
|
|
22
|
+
const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
|
|
23
|
+
const rawSig = secp256k1.sign(hash, pkBytes);
|
|
24
|
+
const sig = secp256k1.Signature.fromBytes(rawSig);
|
|
25
|
+
// Determine recovery bit by trying both and matching the address
|
|
26
|
+
let recovery = 0;
|
|
27
|
+
for (const v of [0, 1]) {
|
|
28
|
+
try {
|
|
29
|
+
const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
|
|
30
|
+
const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
|
|
31
|
+
const addrBytes = keccak_256(pubBytes).slice(-20);
|
|
32
|
+
if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
|
|
33
|
+
recovery = v;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const r = sig.r.toString(16).padStart(64, "0");
|
|
42
|
+
const s = sig.s.toString(16).padStart(64, "0");
|
|
43
|
+
const vHex = (recovery + 27).toString(16).padStart(2, "0");
|
|
44
|
+
return "0x" + r + s + vHex;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get wallet auth headers for the Run402 API.
|
|
48
|
+
* Returns null if no wallet is configured.
|
|
49
|
+
*/
|
|
50
|
+
export function getWalletAuthHeaders(walletPath) {
|
|
51
|
+
const wallet = readWallet(walletPath);
|
|
52
|
+
if (!wallet || !wallet.address || !wallet.privateKey)
|
|
53
|
+
return null;
|
|
54
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
55
|
+
const signature = personalSign(wallet.privateKey, wallet.address, `run402:${timestamp}`);
|
|
56
|
+
return {
|
|
57
|
+
"X-Run402-Wallet": wallet.address,
|
|
58
|
+
"X-Run402-Signature": signature,
|
|
59
|
+
"X-Run402-Timestamp": timestamp,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=wallet-auth.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, renameSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { getWalletPath } from "./config.js";
|
|
5
|
+
export function readWallet(path) {
|
|
6
|
+
const p = path ?? getWalletPath();
|
|
7
|
+
if (!existsSync(p))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function saveWallet(data, path) {
|
|
17
|
+
const p = path ?? getWalletPath();
|
|
18
|
+
const dir = dirname(p);
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
const tmp = join(dir, `.wallet.${randomBytes(4).toString("hex")}.tmp`);
|
|
21
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
22
|
+
renameSync(tmp, p);
|
|
23
|
+
chmodSync(p, 0o600);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=wallet.js.map
|
package/lib/projects.mjs
CHANGED
|
@@ -18,7 +18,7 @@ Subcommands:
|
|
|
18
18
|
usage <id> Show compute/storage usage for a project
|
|
19
19
|
schema <id> Inspect the database schema
|
|
20
20
|
rls <id> <template> <tables_json> Apply Row-Level Security policies
|
|
21
|
-
delete <id>
|
|
21
|
+
delete <id> Soft-delete a project (enters ~104-day grace; renew to reactivate) and remove it from local state
|
|
22
22
|
pin <id> Pin a project (prevents expiry/GC)
|
|
23
23
|
promote-user <id> <email> Promote a user to project_admin role
|
|
24
24
|
demote-user <id> <email> Demote a user from project_admin role
|
package/lib/sender-domain.mjs
CHANGED
|
@@ -9,11 +9,15 @@ Subcommands:
|
|
|
9
9
|
register <domain> [--project <id>] Register a custom sender domain (returns DNS records)
|
|
10
10
|
status [--project <id>] Check domain verification status
|
|
11
11
|
remove [--project <id>] Remove custom sender domain
|
|
12
|
+
inbound-enable <domain> [--project <id>] Enable inbound email (requires DKIM-verified)
|
|
13
|
+
inbound-disable <domain> [--project <id>] Disable inbound email
|
|
12
14
|
|
|
13
15
|
Examples:
|
|
14
16
|
run402 sender-domain register kysigned.com
|
|
15
17
|
run402 sender-domain status
|
|
16
18
|
run402 sender-domain remove
|
|
19
|
+
run402 sender-domain inbound-enable kysigned.com
|
|
20
|
+
run402 sender-domain inbound-disable kysigned.com
|
|
17
21
|
`;
|
|
18
22
|
|
|
19
23
|
function parseFlag(args, flag) {
|
|
@@ -82,12 +86,43 @@ async function remove(args) {
|
|
|
82
86
|
console.log(JSON.stringify(data));
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
async function inboundToggle(action, args) {
|
|
90
|
+
let domain = null;
|
|
91
|
+
let projectOpt = null;
|
|
92
|
+
for (let i = 0; i < args.length; i++) {
|
|
93
|
+
if (args[i] === "--project" && args[i + 1]) { projectOpt = args[++i]; }
|
|
94
|
+
else if (!args[i].startsWith("--") && !domain) { domain = args[i]; }
|
|
95
|
+
}
|
|
96
|
+
const projectId = resolveProjectId(projectOpt);
|
|
97
|
+
const p = findProject(projectId);
|
|
98
|
+
|
|
99
|
+
if (!domain) {
|
|
100
|
+
console.error(JSON.stringify({ status: "error", message: `Missing domain. Usage: run402 sender-domain inbound-${action} <domain>` }));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const method = action === "enable" ? "POST" : "DELETE";
|
|
105
|
+
const res = await fetch(`${API}/email/v1/domains/inbound`, {
|
|
106
|
+
method,
|
|
107
|
+
headers: { apikey: p.service_key, "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify({ domain }),
|
|
109
|
+
});
|
|
110
|
+
const data = await res.json();
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
console.log(JSON.stringify(data, null, 2));
|
|
116
|
+
}
|
|
117
|
+
|
|
85
118
|
export async function run(sub, args) {
|
|
86
119
|
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
87
120
|
switch (sub) {
|
|
88
121
|
case "register": await register(args); break;
|
|
89
122
|
case "status": await status(args); break;
|
|
90
123
|
case "remove": await remove(args); break;
|
|
124
|
+
case "inbound-enable": await inboundToggle("enable", args); break;
|
|
125
|
+
case "inbound-disable": await inboundToggle("disable", args); break;
|
|
91
126
|
default:
|
|
92
127
|
console.error(`Unknown subcommand: ${sub}\n`);
|
|
93
128
|
console.log(HELP);
|
package/package.json
CHANGED