add licensed random user creation flow

This commit is contained in:
2026-04-03 12:22:12 +08:00
parent df9dd80bb3
commit b1f0dbc6f4
6 changed files with 221 additions and 7 deletions

100
server.js
View File

@@ -14,6 +14,8 @@ const {
CLIENT_SECRET,
MAILBOX_ADDRESS = '',
DEFAULT_DOMAIN = '',
DEFAULT_LICENSE_SKU_ID = '',
DEFAULT_USAGE_LOCATION = 'US',
ACCESS_PASSWORD = '',
PORT = '3000',
} = process.env;
@@ -48,6 +50,7 @@ const tokenCache = {
expiresAt: 0,
};
let cachedCreateUserDomain = DEFAULT_DOMAIN || '';
let cachedDefaultLicenseSkuId = DEFAULT_LICENSE_SKU_ID || '';
function timingSafeStringEqual(left, right) {
const leftBuffer = Buffer.from(left);
@@ -278,6 +281,12 @@ function buildTemporaryPassword() {
return `${crypto.randomBytes(12).toString('base64url')}Aa1!`;
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function extractDomainFromMailbox() {
if (DEFAULT_DOMAIN) {
return DEFAULT_DOMAIN;
@@ -413,6 +422,84 @@ async function resolveDomainForUserCreation() {
return cachedCreateUserDomain;
}
async function resolveDefaultLicenseSkuId() {
if (cachedDefaultLicenseSkuId) {
return cachedDefaultLicenseSkuId;
}
const payload = await graphRequest('/subscribedSkus', {
'$select': 'skuId,skuPartNumber,capabilityStatus,consumedUnits,prepaidUnits',
});
const availableSku = (payload.value || []).find((sku) => {
if (sku.capabilityStatus !== 'Enabled') {
return false;
}
const enabled = Number(sku.prepaidUnits?.enabled || 0);
const consumed = Number(sku.consumedUnits || 0);
return enabled === 0 || enabled > consumed;
});
if (!availableSku?.skuId) {
const error = new Error('No available subscribed SKU found for license assignment.');
error.status = 400;
error.details = { code: 'missing_available_sku' };
throw error;
}
cachedDefaultLicenseSkuId = availableSku.skuId;
return cachedDefaultLicenseSkuId;
}
async function enableMailboxLicense(userId) {
const skuId = await resolveDefaultLicenseSkuId();
await graphRequest(`/users/${encodeURIComponent(userId)}`, undefined, {
method: 'PATCH',
json: {
usageLocation: DEFAULT_USAGE_LOCATION,
},
});
let lastError = null;
for (let attempt = 0; attempt < 6; attempt += 1) {
try {
await graphRequest(`/users/${encodeURIComponent(userId)}/assignLicense`, undefined, {
method: 'POST',
json: {
addLicenses: [
{
skuId,
},
],
removeLicenses: [],
},
});
return skuId;
} catch (error) {
lastError = error;
const message = String(error.details?.error?.message || error.message || '').toLowerCase();
const shouldRetry = error.status === 400 && message.includes('usage location');
if (!shouldRetry || attempt === 5) {
throw error;
}
await sleep(2000);
}
}
throw lastError;
}
async function deleteUser(userId) {
await graphRequest(`/users/${encodeURIComponent(userId)}`, undefined, {
method: 'DELETE',
});
}
async function createRandomUser() {
const domain = await resolveDomainForUserCreation();
@@ -444,16 +531,27 @@ async function createRandomUser() {
},
});
let assignedSkuId;
try {
assignedSkuId = await enableMailboxLicense(user.id);
} catch (error) {
await deleteUser(user.id).catch(() => null);
throw error;
}
return {
id: user.id,
displayName: user.displayName || payload.displayName,
userPrincipalName: user.userPrincipalName || payload.userPrincipalName,
mailNickname: payload.mailNickname,
temporaryPassword: payload.password,
usageLocation: DEFAULT_USAGE_LOCATION,
licenseSkuId: assignedSkuId,
};
} catch (error) {
lastError = error;
if (error.status !== 409 && error.status !== 400) {
if (error.status !== 409) {
throw error;
}
}