1.0.4 • Published 4 months ago

ms-oauth-imap v1.0.4

Weekly downloads
-
License
ISC
Repository
github
Last release
4 months ago

ms-oauth-imap

ms-oauth-imap is a Node.js package that integrates Microsoft OAuth authentication with IMAP email access. It provides functions to generate an OAuth URL, retrieve access tokens, refresh tokens, list available mailbox folders, and read emails from specified folders.

Features

  • Generate an OAuth authentication URL
  • Retrieve an access token using an authorization code
  • Refresh expired tokens
  • List available mailbox folders (Inbox, Sent, Drafts, etc.)
  • Read emails from specified mailboxes

Installation

Run the following command to install the package:

npm install ms-oauth-imap

Prerequisites

  • A Microsoft account with access to Outlook email.
  • A registered application on the Azure Portal to obtain your clientId and clientSecret.
  • A configured redirect URI for your application.

Usage

1. Import the Package

const {
  generateAuthUrl,
  getToken,
  refreshToken,
  readMail,
  listFolders,
} = require("ms-oauth-imap");

2. Generate the OAuth URL

const authUrl = generateAuthUrl({
  clientId: "YOUR_CLIENT_ID",
  clientSecret: "YOUR_CLIENT_SECRET",
  redirectUri: "http://localhost:4000/auth/callback",
  scope:
    "openid offline_access https://outlook.office.com/IMAP.AccessAsUser.All",
  state: "optional-state",
});

3. Retrieve the Access Token

const token = await getToken({
  clientId: "YOUR_CLIENT_ID",
  clientSecret: "YOUR_CLIENT_SECRET",
  redirectUri: "http://localhost:4000/auth/callback",
  code: "AUTHORIZATION_CODE_FROM_QUERY",
  scope:
    "openid offline_access https://outlook.office.com/IMAP.AccessAsUser.All",
});

4. Refresh the Token

const refreshedToken = await refreshToken({
  clientId: "YOUR_CLIENT_ID",
  clientSecret: "YOUR_CLIENT_SECRET",
  refreshToken: token.refresh_token,
});

5. List Available Mailbox Folders

const folders = await listFolders({
  userEmail: "YOUR_EMAIL_ADDRESS",
  accessToken: token.access_token,
});

6. Read Emails from a Folder

const emails = await readMail({
  userEmail: "YOUR_EMAIL_ADDRESS",
  accessToken: token.access_token,
  folder: "INBOX", // Can be 'INBOX', 'Sent Items', 'Drafts', etc.
});

Sample Integration with Express

const express = require("express");
const session = require("express-session");
const { simpleParser } = require("mailparser");
const util = require("util");
const {
  generateAuthUrl,
  getToken,
  refreshToken,
  readMail,
  listFolders,
} = require("ms-oauth-imap");
const app = express();

app.use(session({ secret: "secret", resave: false, saveUninitialized: true }));

app.get("/auth", (req, res) => {
  const url = generateAuthUrl({
    clientId: "YOUR_CLIENT_ID",
    clientSecret: "YOUR_CLIENT_SECRET",
    redirectUri: "http://localhost:4000/auth/callback",
    scope:
      "openid offline_access https://outlook.office.com/IMAP.AccessAsUser.All",
  });
  res.redirect(url);
});

app.get("/auth/callback", async (req, res) => {
  const code = req.query.code;
  try {
    const token = await getToken({
      clientId: "YOUR_CLIENT_ID",
      clientSecret: "YOUR_CLIENT_SECRET",
      redirectUri: "http://localhost:4000/auth/callback",
      code,
      scope:
        "openid offline_access https://outlook.office.com/IMAP.AccessAsUser.All",
    });
    req.session.token = token;
    res.send("Authentication successful");
  } catch (error) {
    res.status(500).send(error.message);
  }
});

app.get("/refresh-token", async (req, res) => {
  if (!req.session.token) {
    return res.status(401).send("User not authenticated");
  }
  try {
    const newToken = await refreshToken({
      clientId: "YOUR_CLIENT_ID",
      clientSecret: "YOUR_CLIENT_SECRET",
      refreshToken: req.session.token.refresh_token,
    });
    req.session.token = newToken;
    res.send("Token refreshed successfully");
  } catch (error) {
    res.status(500).send(error.message);
  }
});

app.get("/list-folders", async (req, res) => {
  if (!req.session.token) {
    return res.status(401).send("User not authenticated");
  }

  try {
    const folders = await listFolders({
      userEmail: "YOUR_EMAIL_ADDRESS",
      accessToken: req.session.token.access_token,
    });
    res.send(`<pre>${util.inspect(folders, { depth: null })}</pre>`);
  } catch (error) {
    res.status(500).send(error.message);
  }
});

app.get("/read-mail", async (req, res) => {
  if (!req.session.token) {
    return res.status(401).send("User not authenticated");
  }

  try {
    let accessToken = req.session.token.access_token;

    const expiresAt = req.session.token.expires_at || 0;
    if (Date.now() >= expiresAt) {
      try {
        const newToken = await refreshToken({
          clientId: "YOUR_CLIENT_ID",
          clientSecret: "YOUR_CLIENT_SECRET",
          refreshToken: req.session.token.refresh_token,
        });
        req.session.token = {
          ...newToken,
          expires_at: Date.now() + newToken.expires_in * 1000,
        };
        accessToken = newToken.access_token;
      } catch (refreshError) {
        return res
          .status(401)
          .send('Session expired. Please <a href="/auth">re-authenticate</a>.');
      }
    }

    const mails = await readMail({
      userEmail: "YOUR_EMAIL",
      accessToken,
      folder: "INBOX",
    });

    if (!mails || mails.length === 0) {
      return res.status(404).send("No emails found.");
    }

    let html =
      "<html><head><meta charset='utf-8'><title>Emails</title></head><body>";

    await Promise.all(
      mails.map(async (mail, index) => {
        if (!mail.header || !mail.body) {
          return { error: "Incomplete email data" };
        }

        try {
          const emailContent = `${mail.header}\r\n${mail.body}`;
          const parsed = await simpleParser(emailContent);

          html += `<h2>Email #${index + 1}</h2>`;
          html += `<h3>From:</h3> ${parsed.from?.text || "Unknown Sender"}`;
          html += `<h3>To:</h3> ${parsed.to?.text || "Unknown Recipient"}`;
          html += `<h3>Subject:</h3> ${parsed.subject || "No Subject"}`;
          html += `<h3>Date:</h3> ${parsed.date || "Unknown Date"}`;
          html += `<h3>Body:</h3>${parsed.html || parsed.text || "No Content"}`;
          html += `<hr>`;

          return parsed;
        } catch (parseError) {
          return { error: "Failed to parse email content" };
        }
      })
    );

    html += "</body></html>";
    res.send(html);
  } catch (error) {
    res.status(500).send(error.message);
  }
});

app.listen(4000, () => {
  console.log("Server running on port 4000");
});

Error Handling

This package validates required parameters and throws descriptive errors if any parameter is missing or invalid.

License

MIT License

Contributing

Contributions are welcome. Open an issue or submit a pull request to improve the package.