add licensed random user creation flow
This commit is contained in:
100
server.js
100
server.js
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user