File size: 3,873 Bytes
9646e24 3f13033 9646e24 3f13033 9646e24 3f13033 9646e24 3f13033 9646e24 3f13033 9646e24 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | import { NextResponse } from "next/server";
import { loadConfig } from "@/lib/config";
export const dynamic = "force-dynamic";
export const maxDuration = 10;
const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
const MISSING_CONFIG_ERROR =
"Microsoft OAuth is not configured. The built-in Azure CLI client ID can no longer be used because Microsoft treats it as a first-party application and requires admin pre-authorization. " +
"To connect a Microsoft 365 mailbox, register your own Azure AD application and set AZURE_AD_CLIENT_ID (or AZURE_CLIENT_ID / MICROSOFT_CLIENT_ID) and AZURE_AD_TENANT_ID (or 'common'). " +
"See the README for step-by-step registration instructions.";
/**
* POST /api/microsoft/devicecode
*
* Initiates the OAuth device code flow for Microsoft 365 / Outlook.
* Uses the Azure AD client ID configured via environment variables or the
* app config file. A dedicated app registration is required because the Azure
* CLI client ID is a first-party application that Microsoft no longer allows
* users to consent to directly.
*
* Returns:
* device_code: string β internal code for polling
* user_code: string β code the user enters at the verification URI
* verification_uri: string β URL the user visits to login
* expires_in: number β seconds until the code expires
* interval: number β polling interval in seconds
* message: string β human-readable instructions
*/
export async function POST() {
let config;
try {
config = loadConfig();
} catch (e) {
console.error("[microsoft/devicecode] loadConfig error:", e);
config = { graph: {} };
}
const CLIENT_ID =
process.env.AZURE_AD_CLIENT_ID ||
process.env.AZURE_CLIENT_ID ||
process.env.MICROSOFT_CLIENT_ID ||
config.graph?.clientId ||
"";
const TENANT =
process.env.AZURE_AD_TENANT_ID ||
process.env.AZURE_TENANT_ID ||
process.env.MICROSOFT_TENANT_ID ||
config.graph?.tenantId ||
"common";
if (!CLIENT_ID || !UUID_REGEX.test(CLIENT_ID)) {
return NextResponse.json({ error: MISSING_CONFIG_ERROR }, { status: 400 });
}
const SCOPES = [
"https://graph.microsoft.com/Mail.Read",
"https://graph.microsoft.com/Mail.ReadWrite",
"https://graph.microsoft.com/Files.Read",
"https://graph.microsoft.com/Files.Read.All",
"offline_access",
"openid",
"profile",
];
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 9000);
try {
const res = await fetch(
`https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/devicecode`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: CLIENT_ID,
scope: SCOPES.join(" "),
}),
signal: controller.signal,
}
);
const text = await res.text();
const data = text ? JSON.parse(text) : {};
if (!res.ok) {
return NextResponse.json(
{ error: data.error_description || data.error || "Failed to start device code flow" },
{ status: 500 }
);
}
return NextResponse.json({
device_code: data.device_code,
user_code: data.user_code,
verification_uri: data.verification_uri,
expires_in: data.expires_in,
interval: data.interval || 5,
message: data.message || `Go to ${data.verification_uri} and enter code ${data.user_code}`,
client_id: CLIENT_ID,
tenant_id: TENANT,
scopes: SCOPES,
});
} finally {
clearTimeout(timeout);
}
} catch (e: any) {
return NextResponse.json({ error: e.name === "AbortError" ? "Device code request timed out" : e.message }, { status: 504 });
}
}
|