add user creation and mail body APIs
This commit is contained in:
149
server.js
149
server.js
@@ -13,6 +13,7 @@ const {
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
MAILBOX_ADDRESS = '',
|
||||
DEFAULT_DOMAIN = '',
|
||||
ACCESS_PASSWORD = '',
|
||||
PORT = '3000',
|
||||
} = process.env;
|
||||
@@ -46,6 +47,7 @@ const tokenCache = {
|
||||
accessToken: null,
|
||||
expiresAt: 0,
|
||||
};
|
||||
let cachedCreateUserDomain = DEFAULT_DOMAIN || '';
|
||||
|
||||
function timingSafeStringEqual(left, right) {
|
||||
const leftBuffer = Buffer.from(left);
|
||||
@@ -154,7 +156,7 @@ function parseResponseBody(text) {
|
||||
}
|
||||
}
|
||||
|
||||
async function curlJsonRequest(url, { method = 'GET', headers = {}, form = null } = {}) {
|
||||
async function curlJsonRequest(url, { method = 'GET', headers = {}, form = null, json = null, body = null } = {}) {
|
||||
const args = [
|
||||
'-sS',
|
||||
'-L',
|
||||
@@ -180,6 +182,13 @@ async function curlJsonRequest(url, { method = 'GET', headers = {}, form = null
|
||||
});
|
||||
}
|
||||
|
||||
if (json !== null && json !== undefined) {
|
||||
args.push('-H', 'Content-Type: application/json');
|
||||
args.push('--data', JSON.stringify(json));
|
||||
} else if (body !== null && body !== undefined) {
|
||||
args.push('--data', body);
|
||||
}
|
||||
|
||||
args.push(url);
|
||||
|
||||
try {
|
||||
@@ -247,6 +256,8 @@ async function graphRequest(pathname, query, init = {}) {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
...(init.headers || {}),
|
||||
},
|
||||
json: init.json ?? null,
|
||||
body: init.body ?? null,
|
||||
});
|
||||
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
@@ -259,6 +270,41 @@ async function graphRequest(pathname, query, init = {}) {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
function buildRandomSuffix() {
|
||||
return crypto.randomBytes(4).toString('hex');
|
||||
}
|
||||
|
||||
function buildTemporaryPassword() {
|
||||
return `${crypto.randomBytes(12).toString('base64url')}Aa1!`;
|
||||
}
|
||||
|
||||
function extractDomainFromMailbox() {
|
||||
if (DEFAULT_DOMAIN) {
|
||||
return DEFAULT_DOMAIN;
|
||||
}
|
||||
|
||||
const atIndex = MAILBOX_ADDRESS.indexOf('@');
|
||||
if (atIndex !== -1) {
|
||||
return MAILBOX_ADDRESS.slice(atIndex + 1);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function generateRandomUserPayload() {
|
||||
const suffix = buildRandomSuffix();
|
||||
const mailNickname = `office365-${suffix}`;
|
||||
const domain = extractDomainFromMailbox();
|
||||
const userPrincipalName = domain ? `${mailNickname}@${domain}` : '';
|
||||
|
||||
return {
|
||||
displayName: `Office365 User ${suffix}`,
|
||||
mailNickname,
|
||||
userPrincipalName,
|
||||
password: buildTemporaryPassword(),
|
||||
};
|
||||
}
|
||||
|
||||
async function listUsers() {
|
||||
const users = [];
|
||||
let nextUrl = buildGraphUrl('/users', {
|
||||
@@ -358,6 +404,64 @@ async function getLatestEmail(userId) {
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveDomainForUserCreation() {
|
||||
if (cachedCreateUserDomain) {
|
||||
return cachedCreateUserDomain;
|
||||
}
|
||||
|
||||
cachedCreateUserDomain = extractDomainFromMailbox();
|
||||
return cachedCreateUserDomain;
|
||||
}
|
||||
|
||||
async function createRandomUser() {
|
||||
const domain = await resolveDomainForUserCreation();
|
||||
|
||||
if (!domain) {
|
||||
const error = new Error('Missing DEFAULT_DOMAIN or MAILBOX_ADDRESS domain for user creation.');
|
||||
error.status = 400;
|
||||
error.details = { code: 'missing_domain' };
|
||||
throw error;
|
||||
}
|
||||
|
||||
let lastError = null;
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
const payload = generateRandomUserPayload();
|
||||
payload.userPrincipalName = `${payload.mailNickname}@${domain}`;
|
||||
|
||||
try {
|
||||
const user = await graphRequest('/users', undefined, {
|
||||
method: 'POST',
|
||||
json: {
|
||||
accountEnabled: true,
|
||||
displayName: payload.displayName,
|
||||
mailNickname: payload.mailNickname,
|
||||
userPrincipalName: payload.userPrincipalName,
|
||||
passwordProfile: {
|
||||
forceChangePasswordNextSignIn: true,
|
||||
password: payload.password,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
displayName: user.displayName || payload.displayName,
|
||||
userPrincipalName: user.userPrincipalName || payload.userPrincipalName,
|
||||
mailNickname: payload.mailNickname,
|
||||
temporaryPassword: payload.password,
|
||||
};
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (error.status !== 409 && error.status !== 400) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error('Failed to create random user.');
|
||||
}
|
||||
|
||||
function sendGraphError(response, error) {
|
||||
const graphMessage = error.details?.error?.message;
|
||||
const graphCode = error.details?.error?.code;
|
||||
@@ -452,6 +556,49 @@ app.get('/api/users/:userId/latest-email', async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/users/:userId/latest-email/body', async (request, response) => {
|
||||
try {
|
||||
const user = await getUserById(request.params.userId);
|
||||
|
||||
if (!user.hasMailboxAddress) {
|
||||
response.status(400).json({
|
||||
error: 'This account does not expose a mailbox address in Microsoft Graph.',
|
||||
code: 'mailbox_not_available',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await getLatestEmail(user.id);
|
||||
|
||||
if (!message) {
|
||||
response.json({ user, message: null });
|
||||
return;
|
||||
}
|
||||
|
||||
response.json({
|
||||
user,
|
||||
message: {
|
||||
id: message.id,
|
||||
subject: message.subject,
|
||||
receivedDateTime: message.receivedDateTime,
|
||||
from: message.from,
|
||||
body: message.body,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
sendGraphError(response, error);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/users/random', async (_request, response) => {
|
||||
try {
|
||||
const user = await createRandomUser();
|
||||
response.status(201).json({ user });
|
||||
} catch (error) {
|
||||
sendGraphError(response, error);
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(Number(PORT), () => {
|
||||
console.log(`Office 365 mail console listening on http://localhost:${PORT}`);
|
||||
console.log(`Access password protection: ${authEnabled ? 'enabled' : 'disabled'}`);
|
||||
|
||||
Reference in New Issue
Block a user