adpa-enterprise-framework-automation
Version:
Modular, standards-compliant Node.js/TypeScript automation framework for enterprise requirements, project, and data management. Provides CLI and API for BABOK v3, PMBOK 7th Edition, and DMBOK 2.0 (in progress). Production-ready Express.js API with TypeSpe
209 lines ⢠7.45 kB
JavaScript
/**
* SharePoint OAuth2 Authentication Handler
* Part of ADPA (Automated Document Processing Assistant) v2.1.3
*
* Handles OAuth 2.0 authentication flow for Microsoft Graph API and SharePoint access.
* Implements MSAL (Microsoft Authentication Library) for secure token management.
*
* Features:
* - Interactive authentication flow
* - Token refresh and caching
* - Multi-tenant support
* - Device code flow for headless environments
* - Secure token storage
*/
import { PublicClientApplication } from '@azure/msal-node';
import fs from 'fs';
import path from 'path';
export class SharePointOAuth2 {
config;
msalApp;
tokenCache = new Map();
cacheFilePath;
constructor(config) {
this.config = config;
this.cacheFilePath = config.cacheLocation || path.join(process.cwd(), '.sharepoint-cache.json');
// Configure MSAL
const msalConfig = {
auth: {
clientId: config.clientId,
authority: config.authority || `https://login.microsoftonline.com/${config.tenantId}`,
knownAuthorities: [`${config.tenantId}.b2clogin.com`]
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (!containsPii) {
console.log(`[MSAL ${level}] ${message}`);
}
},
piiLoggingEnabled: false,
logLevel: 3 // Info level
}
}
};
this.msalApp = new PublicClientApplication(msalConfig);
this.loadTokenCache();
}
/**
* Start interactive OAuth2 authentication flow
* @returns Promise with authentication result
*/ async startInteractiveFlow() {
try {
console.log('š Starting device code authentication (recommended for CLI)...');
return await this.startDeviceCodeFlow();
}
catch (error) {
console.error('ā Authentication failed:', error.message);
throw error;
}
}
/**
* Start device code authentication flow (for headless environments)
* @returns Promise with authentication result
*/
async startDeviceCodeFlow() {
try {
const request = {
scopes: this.config.scopes,
deviceCodeCallback: (response) => {
console.log('\nš Device Code Authentication');
console.log(` Go to: ${response.verificationUri}`);
console.log(` Enter code: ${response.userCode}`);
console.log(' Waiting for authentication...\n');
}
};
console.log('š Starting device code authentication...');
const authResult = await this.msalApp.acquireTokenByDeviceCode(request);
if (authResult) {
await this.saveTokenCache(authResult);
console.log('ā
Device code authentication successful');
return authResult;
}
else {
throw new Error('Device code authentication failed');
}
}
catch (error) {
console.error('ā Device code authentication failed:', error.message);
throw error;
}
}
/**
* Get valid access token (handles refresh automatically)
* @returns Valid access token
*/
async getValidAccessToken() {
try { // Try to get cached account
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
if (accounts.length === 0) {
throw new Error('No cached accounts found. Please authenticate first.');
}
const account = accounts[0];
// Try silent token acquisition
const silentRequest = {
scopes: this.config.scopes,
account: account
};
try {
const result = await this.msalApp.acquireTokenSilent(silentRequest);
if (result?.accessToken) {
return result.accessToken;
}
}
catch (silentError) {
console.log('ā ļø Silent token acquisition failed, trying refresh...');
}
// If silent acquisition fails, try interactive flow
console.log('š Refreshing authentication...');
const authResult = await this.startInteractiveFlow();
return authResult.accessToken;
}
catch (error) {
console.error('ā Failed to get valid access token:', error.message);
throw error;
}
}
/**
* Check if user is currently authenticated
* @returns Boolean indicating authentication status
*/
async isAuthenticated() {
try {
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
return accounts.length > 0;
}
catch (error) {
return false;
}
}
/**
* Get current user account information
* @returns Account information or null
*/
async getCurrentAccount() {
try {
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
return accounts.length > 0 ? accounts[0] : null;
}
catch (error) {
return null;
}
}
/**
* Sign out current user
*/
async signOut() {
try {
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
for (const account of accounts) {
await this.msalApp.getTokenCache().removeAccount(account);
}
// Clear local cache
this.tokenCache.clear();
if (fs.existsSync(this.cacheFilePath)) {
fs.unlinkSync(this.cacheFilePath);
}
console.log('ā
Successfully signed out');
}
catch (error) {
console.error('ā Sign out error:', error.message);
throw error;
}
} /**
* Load token cache from file
*/
loadTokenCache() {
try {
if (fs.existsSync(this.cacheFilePath)) {
const cacheData = fs.readFileSync(this.cacheFilePath, 'utf-8');
const cache = JSON.parse(cacheData);
this.tokenCache = new Map(Object.entries(cache));
console.log('ā
Token cache loaded');
}
}
catch (error) {
console.log('ā ļø Could not load token cache, starting fresh');
this.tokenCache = new Map();
}
}
/**
* Save token cache to file
* @param authResult Authentication result to cache
*/
async saveTokenCache(authResult) {
try {
const cacheData = {
accessToken: authResult.accessToken,
expiresOn: authResult.expiresOn?.toISOString(),
account: authResult.account
};
fs.writeFileSync(this.cacheFilePath, JSON.stringify(cacheData, null, 2));
console.log('ā
Token cache saved');
}
catch (error) {
console.warn('ā ļø Could not save token cache:', error.message);
}
}
}
//# sourceMappingURL=SharePointOAuth2.js.map