- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
180 lines
5.6 KiB
JavaScript
180 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require("fs");
|
|
const https = require("https");
|
|
const { homedir } = require("os");
|
|
const { join } = require("path");
|
|
const { createServer } = require("http");
|
|
|
|
const CREDS_PATH = join(homedir(), ".openclaw/secrets/outlook-oauth.json");
|
|
const REDIRECT_URI = "http://localhost:8765";
|
|
|
|
// Read existing credentials to get client_id
|
|
let existingCreds = {};
|
|
try {
|
|
existingCreds = JSON.parse(fs.readFileSync(CREDS_PATH, "utf8"));
|
|
} catch (err) {
|
|
console.error("❌ Could not read existing credentials:", err.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
const CLIENT_ID = existingCreds.client_id;
|
|
|
|
// Updated scopes with ReadWrite permissions
|
|
const SCOPES = [
|
|
"https://graph.microsoft.com/Mail.ReadWrite",
|
|
"https://graph.microsoft.com/MailboxSettings.ReadWrite",
|
|
"offline_access",
|
|
].join(" ");
|
|
|
|
function httpsRequest(url, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = https.request(url, options, (res) => {
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => {
|
|
if (res.statusCode >= 400) {
|
|
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
} else {
|
|
resolve(JSON.parse(data));
|
|
}
|
|
});
|
|
});
|
|
req.on("error", reject);
|
|
if (options.body) req.write(options.body);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function exchangeCode(code) {
|
|
const body = new URLSearchParams({
|
|
client_id: CLIENT_ID,
|
|
code: code,
|
|
redirect_uri: REDIRECT_URI,
|
|
grant_type: "authorization_code",
|
|
scope: SCOPES,
|
|
}).toString();
|
|
|
|
const data = await httpsRequest("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Content-Length": Buffer.byteLength(body),
|
|
},
|
|
body,
|
|
});
|
|
|
|
return {
|
|
client_id: CLIENT_ID,
|
|
refresh_token: data.refresh_token,
|
|
access_token: data.access_token,
|
|
scope: SCOPES,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
async function main() {
|
|
console.log("🔐 Outlook Re-authorization (ReadWrite permissions)\n");
|
|
console.log("This will grant permissions to:");
|
|
console.log(" - Read emails");
|
|
console.log(" - Move/delete emails");
|
|
console.log(" - Create inbox rules");
|
|
console.log(" - Block spam automatically\n");
|
|
|
|
const authUrl = `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?${new URLSearchParams(
|
|
{
|
|
client_id: CLIENT_ID,
|
|
response_type: "code",
|
|
redirect_uri: REDIRECT_URI,
|
|
scope: SCOPES,
|
|
response_mode: "query",
|
|
prompt: "consent", // Force consent screen to show new permissions
|
|
},
|
|
)}`;
|
|
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
console.log("📋 Open this URL in your browser:\n");
|
|
console.log(authUrl);
|
|
console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
console.log("Waiting for authorization...\n");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const server = createServer(async (req, res) => {
|
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
const code = url.searchParams.get("code");
|
|
const error = url.searchParams.get("error");
|
|
|
|
if (error) {
|
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
res.end(`<h1>❌ Authorization failed</h1><p>${error}</p><p>You can close this window.</p>`);
|
|
server.close();
|
|
reject(new Error(error));
|
|
return;
|
|
}
|
|
|
|
if (!code) {
|
|
res.writeHead(400);
|
|
res.end("Missing code parameter");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log("✅ Authorization code received, exchanging for tokens...\n");
|
|
const creds = await exchangeCode(code);
|
|
|
|
fs.writeFileSync(CREDS_PATH, JSON.stringify(creds, null, 2));
|
|
fs.chmodSync(CREDS_PATH, 0o600);
|
|
|
|
console.log(`✅ Credentials saved to: ${CREDS_PATH}\n`);
|
|
console.log("Permissions granted:");
|
|
console.log(" ✓ Mail.ReadWrite");
|
|
console.log(" ✓ MailboxSettings.ReadWrite");
|
|
console.log(" ✓ offline_access\n");
|
|
|
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
res.end(`
|
|
<h1>✅ Authorization successful!</h1>
|
|
<p>You can close this window and return to the terminal.</p>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 2em; }
|
|
h1 { color: #28a745; }
|
|
</style>
|
|
`);
|
|
|
|
server.close();
|
|
resolve();
|
|
} catch (err) {
|
|
console.error("❌ Token exchange failed:", err.message);
|
|
res.writeHead(500, { "Content-Type": "text/html" });
|
|
res.end(`<h1>❌ Error</h1><p>${err.message}</p>`);
|
|
server.close();
|
|
reject(err);
|
|
}
|
|
});
|
|
|
|
server.listen(8765, () => {
|
|
console.log("🌐 Callback server listening on http://localhost:8765\n");
|
|
});
|
|
|
|
server.on("error", (err) => {
|
|
if (err.code === "EADDRINUSE") {
|
|
console.error("❌ Port 8765 is already in use. Stop other processes and try again.");
|
|
} else {
|
|
console.error("❌ Server error:", err.message);
|
|
}
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
main()
|
|
.then(() => {
|
|
console.log("✅ Outlook re-authorization complete!\n");
|
|
console.log("You can now block spam with: node block-dating-spam.cjs\n");
|
|
process.exit(0);
|
|
})
|
|
.catch((err) => {
|
|
console.error("❌ Re-authorization failed:", err.message);
|
|
process.exit(1);
|
|
});
|