@msout/microsoft-webauth 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  A tool to authenticate against microsoft online (live or professionnal tenants)
4
4
  We do not rely on GraphAPI.
5
- e.g:
5
+
6
+ e.g:
6
7
  - https://onenote.cloud.microsoft/notebooks
7
8
  - https://outlook.live.com/mail/
8
9
  - and by extension https://login.microsoft.com
@@ -18,6 +19,13 @@ This is a standalone CLI tool for authenticating with Microsoft accounts using P
18
19
  - MFA/2FA support (OTC codes, number matching)
19
20
  - Session persistence
20
21
 
22
+ ## Why this project ?
23
+
24
+ While this let you authenticate this is a part of a bigger purpose,
25
+ primary aim is to offer people a simple way to get out of Microsoft OneNote, because you regardless of what ms documentation states
26
+ => https://learn.microsoft.com/en-us/answers/questions/2276682/onenote-api-fails-with-large-sharepoint-document-l
27
+
28
+ in essence you want to search for microsoft-onenote-list-notebook, microsoft-onenote-exporter
21
29
 
22
30
  ## Available on npmjs
23
31
 
@@ -43,6 +51,12 @@ npm install @msout/microsoft-webauth
43
51
  microsoft-webauth login --email your@email.com --password yourpassword
44
52
  ```
45
53
 
54
+ With custom auth file path:
55
+
56
+ ```bash
57
+ microsoft-webauth login --email your@email.com --password yourpassword --auth-file /path/to/authfile.json
58
+ ```
59
+
46
60
  ### Login (Manual/Interactive)
47
61
 
48
62
  ```bash
@@ -51,18 +65,36 @@ microsoft-webauth login
51
65
 
52
66
  This will open a browser window. Log in manually, then press Enter when you see the notebooks list.
53
67
 
68
+ With custom auth file path:
69
+
70
+ ```bash
71
+ microsoft-webauth login --auth-file /path/to/authfile.json
72
+ ```
73
+
54
74
  ### Check Authentication Status
55
75
 
56
76
  ```bash
57
77
  microsoft-webauth check
58
78
  ```
59
79
 
80
+ With custom auth file path:
81
+
82
+ ```bash
83
+ microsoft-webauth check --auth-file /path/to/authfile.json
84
+ ```
85
+
60
86
  ### Logout
61
87
 
62
88
  ```bash
63
89
  microsoft-webauth logout
64
90
  ```
65
91
 
92
+ With custom auth file path:
93
+
94
+ ```bash
95
+ microsoft-webauth logout --auth-file /path/to/authfile.json
96
+ ```
97
+
66
98
  ## Options
67
99
 
68
100
  | Option | Description |
@@ -71,13 +103,16 @@ microsoft-webauth logout
71
103
  | `--password <password>` | Microsoft account password (for automated login) |
72
104
  | `--notheadless` | Run in visible browser mode (disable headless) |
73
105
  | `--dodump` | Dump HTML content to files for debugging |
106
+ | `--auth-file <path>` | Path to auth file (default: ~/.microsoft-webauth/auth-file.json) |
74
107
 
75
108
  ## Output
76
109
 
77
- Authentication state is saved to `auth.json` in the project directory. A metadata file `auth-meta.json` stores:
110
+ Authentication state is saved to the specified auth file path. By default, it uses `~/.microsoft-webauth/auth-file.json`. A metadata file `{auth-file-prefix}-meta.json` stores:
78
111
  - Email used for login
79
112
  - Login timestamp
80
113
 
114
+ When files already exist, they are automatically backed up with `.old` suffix. If `.old` files already exist, a warning is displayed before overwriting.
115
+
81
116
  ## Testing
82
117
 
83
118
  ```bash
@@ -108,3 +143,4 @@ microsoft-webauth-playwright/
108
143
  ## License
109
144
 
110
145
  ISC — same as MSOneNote Exporter.
146
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msout/microsoft-webauth",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Microsoft web authentication module, using playwright to automate the login process and retrieve cookies for authenticated sessions.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/auth.js CHANGED
@@ -6,27 +6,94 @@
6
6
  const { chromium } = require('playwright');
7
7
  const fs = require('fs-extra');
8
8
  const logger = require('./utils/logger');
9
- const { AUTH_FILE, ONENOTE_URL, OUTLOOK_URL } = require('./config');
9
+ const { DEFAULT_AUTH_FILE, getAuthMetaFilePath, ensureAuthDir, ONENOTE_URL } = require('./config');
10
10
  const path = require('path');
11
11
  const readline = require('readline');
12
12
 
13
- // Companion metadata file — stores email + login time for display
14
- const AUTH_META_FILE = AUTH_FILE.replace('auth.json', 'auth-meta.json');
13
+ /** Returns the auth file path, defaulting to DEFAULT_AUTH_FILE */
14
+ function getAuthFilePath(authFilePath) {
15
+ return authFilePath || DEFAULT_AUTH_FILE;
16
+ }
15
17
 
16
18
  /** Returns { email, loginTime } from auth-meta.json, or null if not found. */
17
- async function getAuthMeta() {
19
+ async function getAuthMeta(authFilePath) {
20
+ const filePath = getAuthFilePath(authFilePath);
21
+ const metaPath = getAuthMetaFilePath(filePath);
18
22
  try {
19
- if (await fs.pathExists(AUTH_META_FILE)) {
20
- return await fs.readJson(AUTH_META_FILE);
23
+ if (await fs.pathExists(metaPath)) {
24
+ return await fs.readJson(metaPath);
21
25
  }
22
26
  } catch (e) { }
23
27
  return null;
24
28
  }
25
29
 
30
+ /** Generates backup path by appending .old to file */
31
+ function getBackupPath(filePath) {
32
+ return filePath + '.old';
33
+ }
34
+
35
+ /**
36
+ * Checks if files exist and handles backup logic
37
+ * Returns object with { authFileExists, metaFileExists, willOverwriteOld }
38
+ */
39
+ async function checkAndPrepareFiles(authFilePath) {
40
+ const metaFilePath = getAuthMetaFilePath(authFilePath);
41
+
42
+ const authFileExists = await fs.pathExists(authFilePath);
43
+ const metaFileExists = await fs.pathExists(metaFilePath);
44
+
45
+ // Check if .old versions exist
46
+ const authOldPath = getBackupPath(authFilePath);
47
+ const metaOldPath = getBackupPath(metaFilePath);
48
+ const authOldExists = await fs.pathExists(authOldPath);
49
+ const metaOldExists = await fs.pathExists(metaOldPath);
50
+
51
+ let willOverwriteOld = false;
52
+
53
+ // If current files exist, backup them
54
+ if (authFileExists || metaFileExists) {
55
+ // Warn if .old files already exist (they will be erased)
56
+ if (authOldExists || metaOldExists) {
57
+ logger.warn(`Warning: Backup files (.old) already exist and will be erased:`);
58
+ if (authOldExists) logger.warn(` ${authOldPath}`);
59
+ if (metaOldExists) logger.warn(` ${metaOldPath}`);
60
+ willOverwriteOld = true;
61
+ }
62
+
63
+ // Create backup directory if it doesn't exist
64
+ const dir = path.dirname(authFilePath);
65
+ await ensureAuthDir(authFilePath);
66
+
67
+ // Backup existing files
68
+ if (authFileExists) {
69
+ await fs.move(authFilePath, authOldPath, { overwrite: true });
70
+ logger.info(`Backed up ${authFilePath} to ${authOldPath}`);
71
+ }
72
+ if (metaFileExists) {
73
+ await fs.move(metaFilePath, metaOldPath, { overwrite: true });
74
+ logger.info(`Backed up ${metaFilePath} to ${metaOldPath}`);
75
+ }
76
+ } else {
77
+ // Ensure directory exists for new files
78
+ await ensureAuthDir(authFilePath);
79
+ }
80
+
81
+ return {
82
+ authFileExists,
83
+ metaFileExists,
84
+ willOverwriteOld,
85
+ authOldPath,
86
+ metaOldPath
87
+ };
88
+ }
89
+
26
90
  /** Deletes auth.json and auth-meta.json (full logout). */
27
- async function logout() {
28
- await fs.remove(AUTH_FILE);
29
- await fs.remove(AUTH_META_FILE);
91
+ async function logout(authFilePath) {
92
+ const filePath = getAuthFilePath(authFilePath);
93
+ const metaPath = getAuthMetaFilePath(filePath);
94
+
95
+ await fs.remove(filePath);
96
+ await fs.remove(metaPath);
30
97
  }
31
98
 
32
99
  /**
@@ -63,15 +130,20 @@ async function waitForAuthSuccess(page, targetUrl) {
63
130
  ]);
64
131
  logger.success('Outlook mail interface detected.');
65
132
  } else {
66
- // OneNote default behavior
67
- logger.info('Waiting for redirection to notebooks list...');
133
+ // OneNote default behavior — wait for the authenticated notebooks interface.
134
+ // IMPORTANT: We must require /notebooks in the URL to avoid matching the
135
+ // unauthenticated marketing landing page (onenote.cloud.microsoft/en-us)
136
+ // which also matches the old hostname-only check and caused premature auth saving.
137
+ logger.info('Waiting for redirection to authenticated notebooks interface...');
68
138
  await Promise.any([
69
- page.waitForURL(url => url.toString().includes('/notebooks') || url.hostname.includes('onenote.cloud.microsoft') || url.hostname.includes('onenote.com'), { timeout: 60000 }),
139
+ // Primary: URL must contain /notebooks (authenticated app)
140
+ page.waitForURL(url => url.toString().includes('/notebooks'), { timeout: 60000 }),
141
+ // Fallback UI elements that only appear when actually signed in
70
142
  page.waitForSelector('text="My notebooks"', { state: 'visible', timeout: 60000 }),
71
143
  page.waitForSelector('text="Create new notebook"', { state: 'visible', timeout: 60000 }),
72
- page.waitForSelector('text="Welcome, "', { state: 'visible', timeout: 60000 })
144
+ page.waitForSelector('text="Welcome, "', { state: 'visible', timeout: 60000 }),
73
145
  ]);
74
- logger.success('Notebooks list detected.');
146
+ logger.success('Authenticated notebooks interface detected.');
75
147
  }
76
148
  }
77
149
 
@@ -130,15 +202,22 @@ async function dismissFidoPage(page, logger) {
130
202
  }
131
203
 
132
204
  async function login(credentials = {}) {
133
- const { email, password, targetUrl } = credentials;
205
+ const { email, password, targetUrl, authFile } = credentials;
134
206
  const isAutomated = !!(email && password);
135
207
  const headless = !credentials.notheadless && isAutomated;
136
208
  // Use targetUrl if provided, otherwise default to ONENOTE_URL for backward compatibility
137
209
  const finalTargetUrl = targetUrl || ONENOTE_URL;
138
210
 
211
+ // Get the auth file path (use provided or default)
212
+ const filePath = getAuthFilePath(authFile);
213
+ const metaPath = getAuthMetaFilePath(filePath);
214
+
139
215
  // Added to verify version on user's machine
140
216
  logger.debug('Authentication Module: Version 4.4-DEBUG starting...');
141
217
 
218
+ logger.debug(`Using auth file path: ${filePath}`);
219
+ logger.debug(`Using meta file path: ${metaPath}`);
220
+
142
221
  if (isAutomated) {
143
222
  logger.info(`Attempting automated login for ${email}...`);
144
223
  } else {
@@ -147,6 +226,9 @@ async function login(credentials = {}) {
147
226
  const serviceName = finalTargetUrl.includes('outlook') ? 'Outlook' : 'OneNote';
148
227
  logger.warn(`The script will wait until you successfully reach the ${serviceName} interface.`);
149
228
  }
229
+
230
+ // Prepare files (backup existing if needed, create directory)
231
+ await checkAndPrepareFiles(filePath);
150
232
 
151
233
  const browser = await chromium.launch({ headless: !!headless });
152
234
  const context = await browser.newContext({
@@ -570,14 +652,14 @@ async function login(credentials = {}) {
570
652
  }
571
653
 
572
654
  logger.info('Saving authentication state...');
573
- await context.storageState({ path: AUTH_FILE });
655
+ await context.storageState({ path: filePath });
574
656
 
575
- await fs.writeJson(AUTH_META_FILE, {
657
+ await fs.writeJson(metaPath, {
576
658
  email: email || 'manual login',
577
659
  loginTime: new Date().toISOString()
578
660
  });
579
661
 
580
- logger.success(`Authentication successful! State saved to ${AUTH_FILE}`);
662
+ logger.success(`Authentication successful! State saved to ${filePath}`);
581
663
  } catch (error) {
582
664
  logger.error('Authentication failed or cancelled:', error);
583
665
  if (isAutomated) {
@@ -588,16 +670,19 @@ async function login(credentials = {}) {
588
670
  }
589
671
  }
590
672
 
591
- async function getAuthenticatedContext(browser) {
592
- if (await fs.pathExists(AUTH_FILE)) {
593
- return browser.newContext({ storageState: AUTH_FILE });
673
+ async function getAuthenticatedContext(browser, authFilePath) {
674
+ const filePath = getAuthFilePath(authFilePath);
675
+ if (await fs.pathExists(filePath)) {
676
+ return browser.newContext({ storageState: filePath });
594
677
  } else {
595
678
  throw new Error('No authentication state found. Please run "login" command first.');
596
679
  }
597
680
  }
598
681
 
599
- async function checkAuth(targetUrl = ONENOTE_URL) {
600
- if (!(await fs.pathExists(AUTH_FILE))) {
682
+ async function checkAuth(targetUrl = ONENOTE_URL, authFilePath) {
683
+ const filePath = getAuthFilePath(authFilePath);
684
+
685
+ if (!(await fs.pathExists(filePath))) {
601
686
  return false;
602
687
  }
603
688
 
@@ -605,7 +690,7 @@ async function checkAuth(targetUrl = ONENOTE_URL) {
605
690
  try {
606
691
  logger.debug('Verifying authentication session...');
607
692
  browser = await chromium.launch({ headless: true });
608
- const context = await browser.newContext({ storageState: AUTH_FILE });
693
+ const context = await browser.newContext({ storageState: filePath });
609
694
  const page = await context.newPage();
610
695
 
611
696
  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
@@ -618,7 +703,7 @@ async function checkAuth(targetUrl = ONENOTE_URL) {
618
703
 
619
704
  if (isLoginUrl) {
620
705
  logger.warn('Authentication session has expired. Deleting stale auth state.');
621
- await logout();
706
+ await logout(authFilePath);
622
707
  return false;
623
708
  }
624
709
 
package/src/config.js CHANGED
@@ -4,24 +4,46 @@
4
4
  * @copyright 2026 phptr,enoola,msout
5
5
  */
6
6
  const path = require('path');
7
+ const os = require('os');
8
+ const fs = require('fs-extra');
7
9
 
8
10
  /**
9
- * Returns the directory where auth files (auth.json, auth-meta.json) are stored.
10
- * For this standalone CLI tool, we use the project root.
11
+ * Returns the default auth file path (~/.microsoft-webauth/auth-file.json)
11
12
  */
12
- function getUserDataDir() {
13
- return path.resolve(__dirname, '..');
13
+ function getDefaultAuthFilePath() {
14
+ const homeDir = os.homedir();
15
+ const appDir = path.join(homeDir, '.microsoft-webauth');
16
+ return path.join(appDir, 'auth-file.json');
14
17
  }
15
18
 
16
- const USER_DATA_DIR = getUserDataDir();
19
+ /**
20
+ * Generates the meta file path from auth file path
21
+ * e.g., /path/to/auth-file.json -> /path/to/auth-file-meta.json
22
+ */
23
+ function getAuthMetaFilePath(authFilePath) {
24
+ const dir = path.dirname(authFilePath);
25
+ const name = path.basename(authFilePath);
26
+ // Remove .json extension if present, then add -meta.json
27
+ const baseName = name.replace(/\.json$/, '');
28
+ return path.join(dir, `${baseName}-meta.json`);
29
+ }
30
+
31
+ /**
32
+ * Ensures the directory for the auth file exists, creates it if needed
33
+ */
34
+ async function ensureAuthDir(authFilePath) {
35
+ const dir = path.dirname(authFilePath);
36
+ await fs.ensureDir(dir);
37
+ }
17
38
 
18
- const AUTH_FILE = path.join(USER_DATA_DIR, 'auth.json');
19
- const ONENOTE_URL = 'https://onenote.cloud.microsoft/en-us';
39
+ const DEFAULT_AUTH_FILE = getDefaultAuthFilePath();
40
+ const ONENOTE_URL = 'https://onenote.cloud.microsoft/notebooks';
20
41
  const OUTLOOK_URL = 'https://outlook.cloud.microsoft/mail/';
21
42
 
22
43
  module.exports = {
23
- AUTH_FILE,
44
+ DEFAULT_AUTH_FILE,
45
+ getAuthMetaFilePath,
46
+ ensureAuthDir,
24
47
  ONENOTE_URL,
25
48
  OUTLOOK_URL,
26
- USER_DATA_DIR,
27
49
  };
package/src/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
  const { program } = require('commander');
8
8
  const logger = require('./utils/logger');
9
9
  const { login, checkAuth, getAuthMeta, logout } = require('./auth');
10
- const { ONENOTE_URL, OUTLOOK_URL } = require('./config');
10
+ const { DEFAULT_AUTH_FILE, ONENOTE_URL, OUTLOOK_URL } = require('./config');
11
11
 
12
12
  program
13
13
  .name('webauth')
@@ -22,6 +22,7 @@ program
22
22
  .option('--notheadless', 'Run in visible browser mode for debugging')
23
23
  .option('--dodump', 'Dump HTML content to files for debugging')
24
24
  .option('--against <target>', 'Target service: onenote (default) or outlook', 'onenote')
25
+ .option('--auth-file <path>', 'Path to auth file (default: ~/.microsoft-webauth/auth-file.json)', DEFAULT_AUTH_FILE)
25
26
  .action(async (options) => {
26
27
  const targetUrl = options.against === 'outlook' ? OUTLOOK_URL : ONENOTE_URL;
27
28
  await login({ ...options, targetUrl });
@@ -31,12 +32,13 @@ program
31
32
  .command('check')
32
33
  .description('Check if authenticated')
33
34
  .option('--against <target>', 'Target service: onenote (default) or outlook', 'onenote')
35
+ .option('--auth-file <path>', 'Path to auth file (default: ~/.microsoft-webauth/auth-file.json)', DEFAULT_AUTH_FILE)
34
36
  .action(async (options) => {
35
37
  const targetUrl = options.against === 'outlook' ? OUTLOOK_URL : ONENOTE_URL;
36
- const isAuth = await checkAuth(targetUrl);
38
+ const isAuth = await checkAuth(targetUrl, options.authFile);
37
39
  if (isAuth) {
38
40
  logger.success('Authentication file found. You are authenticated.');
39
- const meta = await getAuthMeta();
41
+ const meta = await getAuthMeta(options.authFile);
40
42
  if (meta && meta.email) {
41
43
  const loginTime = new Date(meta.loginTime).toLocaleString();
42
44
  logger.info(`Logged in as: ${meta.email}`);
@@ -50,8 +52,9 @@ program
50
52
  program
51
53
  .command('logout')
52
54
  .description('Clear authentication state')
53
- .action(async () => {
54
- await logout();
55
+ .option('--auth-file <path>', 'Path to auth file (default: ~/.microsoft-webauth/auth-file.json)', DEFAULT_AUTH_FILE)
56
+ .action(async (options) => {
57
+ await logout(options.authFile);
55
58
  logger.success('Logged out successfully. Authentication state cleared.');
56
59
  });
57
60