@msout/microsoft-webauth 0.0.6 → 0.0.8

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 enola/phptr/msout
3
+ Copyright (c) 2026 enoola/phptr/msout
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,18 +1,32 @@
1
1
  # microsoft-webauth
2
2
 
3
3
  A tool to authenticate against microsoft online (live or professionnal tenants)
4
- e.g:
4
+ We do not rely on GraphAPI.
5
+
6
+ e.g:
5
7
  - https://onenote.cloud.microsoft/notebooks
6
8
  - https://outlook.live.com/mail/
7
9
  - and by extension https://login.microsoft.com
8
10
 
9
- Microsoft web authentication via Playwright — extracted from [MSOneNote Exporter](https://github.com/msout/Microsoft-OneNote-Exporter).
11
+ Microsoft web authentication via Playwright — extracted from [MSOneNote Exporter](https://github.com/enoola/Microsoft-OneNote-Exporter).
12
+ I did extract it realising it might be useful out of the box for other projects.
13
+ And it will make it easier to maintain as far as I see it.
10
14
 
11
15
  This is a standalone CLI tool for authenticating with Microsoft accounts using Playwright. It handles:
16
+ - NO GraphAPI
12
17
  - Automated login with email/password
13
18
  - Manual login in browser
14
19
  - MFA/2FA support (OTC codes, number matching)
15
20
  - Session persistence
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
29
+
16
30
  ## Available on npmjs
17
31
 
18
32
  You can find this package here: https://www.npmjs.com/package/@msout/microsoft-webauth
@@ -37,6 +51,12 @@ npm install @msout/microsoft-webauth
37
51
  microsoft-webauth login --email your@email.com --password yourpassword
38
52
  ```
39
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
+
40
60
  ### Login (Manual/Interactive)
41
61
 
42
62
  ```bash
@@ -45,18 +65,36 @@ microsoft-webauth login
45
65
 
46
66
  This will open a browser window. Log in manually, then press Enter when you see the notebooks list.
47
67
 
68
+ With custom auth file path:
69
+
70
+ ```bash
71
+ microsoft-webauth login --auth-file /path/to/authfile.json
72
+ ```
73
+
48
74
  ### Check Authentication Status
49
75
 
50
76
  ```bash
51
77
  microsoft-webauth check
52
78
  ```
53
79
 
80
+ With custom auth file path:
81
+
82
+ ```bash
83
+ microsoft-webauth check --auth-file /path/to/authfile.json
84
+ ```
85
+
54
86
  ### Logout
55
87
 
56
88
  ```bash
57
89
  microsoft-webauth logout
58
90
  ```
59
91
 
92
+ With custom auth file path:
93
+
94
+ ```bash
95
+ microsoft-webauth logout --auth-file /path/to/authfile.json
96
+ ```
97
+
60
98
  ## Options
61
99
 
62
100
  | Option | Description |
@@ -65,13 +103,16 @@ microsoft-webauth logout
65
103
  | `--password <password>` | Microsoft account password (for automated login) |
66
104
  | `--notheadless` | Run in visible browser mode (disable headless) |
67
105
  | `--dodump` | Dump HTML content to files for debugging |
106
+ | `--auth-file <path>` | Path to auth file (default: ~/.microsoft-webauth/auth-file.json) |
68
107
 
69
108
  ## Output
70
109
 
71
- 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:
72
111
  - Email used for login
73
112
  - Login timestamp
74
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
+
75
116
  ## Testing
76
117
 
77
118
  ```bash
@@ -102,3 +143,4 @@ microsoft-webauth-playwright/
102
143
  ## License
103
144
 
104
145
  ISC — same as MSOneNote Exporter.
146
+
package/jest.config.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @fileoverview Jest configuration.
3
+ * @author phptr,enoola,msout
4
+ * @copyright 2026 phptr,enoola,msout
5
+ */
1
6
  module.exports = {
2
7
  testEnvironment: 'node',
3
8
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msout/microsoft-webauth",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
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": {
@@ -22,7 +22,7 @@
22
22
  "javascript"
23
23
  ],
24
24
  "author": "msout@tuta.io",
25
- "license": "MIT",
25
+ "license": "Apache-2.0",
26
26
  "repository": {
27
27
  "type": "git",
28
28
  "url": "git+https://github.com/Ms-OneNote-Exporter/microsoft-webauth.git"
package/src/auth.js CHANGED
@@ -1,27 +1,99 @@
1
+ /**
2
+ * @fileoverview This file handles user authentication logic.
3
+ * @author phptr,enoola,msout
4
+ * @copyright 2026 phptr,enoola,msout
5
+ */
1
6
  const { chromium } = require('playwright');
2
7
  const fs = require('fs-extra');
3
8
  const logger = require('./utils/logger');
4
- const { AUTH_FILE, ONENOTE_URL, OUTLOOK_URL } = require('./config');
9
+ const { DEFAULT_AUTH_FILE, getAuthMetaFilePath, ensureAuthDir, ONENOTE_URL } = require('./config');
5
10
  const path = require('path');
6
11
  const readline = require('readline');
7
12
 
8
- // Companion metadata file — stores email + login time for display
9
- 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
+ }
10
17
 
11
18
  /** Returns { email, loginTime } from auth-meta.json, or null if not found. */
12
- async function getAuthMeta() {
19
+ async function getAuthMeta(authFilePath) {
20
+ const filePath = getAuthFilePath(authFilePath);
21
+ const metaPath = getAuthMetaFilePath(filePath);
13
22
  try {
14
- if (await fs.pathExists(AUTH_META_FILE)) {
15
- return await fs.readJson(AUTH_META_FILE);
23
+ if (await fs.pathExists(metaPath)) {
24
+ return await fs.readJson(metaPath);
16
25
  }
17
26
  } catch (e) { }
18
27
  return null;
19
28
  }
20
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
+
21
90
  /** Deletes auth.json and auth-meta.json (full logout). */
22
- async function logout() {
23
- await fs.remove(AUTH_FILE);
24
- 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);
25
97
  }
26
98
 
27
99
  /**
@@ -125,15 +197,22 @@ async function dismissFidoPage(page, logger) {
125
197
  }
126
198
 
127
199
  async function login(credentials = {}) {
128
- const { email, password, targetUrl } = credentials;
200
+ const { email, password, targetUrl, authFile } = credentials;
129
201
  const isAutomated = !!(email && password);
130
202
  const headless = !credentials.notheadless && isAutomated;
131
203
  // Use targetUrl if provided, otherwise default to ONENOTE_URL for backward compatibility
132
204
  const finalTargetUrl = targetUrl || ONENOTE_URL;
133
205
 
206
+ // Get the auth file path (use provided or default)
207
+ const filePath = getAuthFilePath(authFile);
208
+ const metaPath = getAuthMetaFilePath(filePath);
209
+
134
210
  // Added to verify version on user's machine
135
211
  logger.debug('Authentication Module: Version 4.4-DEBUG starting...');
136
212
 
213
+ logger.debug(`Using auth file path: ${filePath}`);
214
+ logger.debug(`Using meta file path: ${metaPath}`);
215
+
137
216
  if (isAutomated) {
138
217
  logger.info(`Attempting automated login for ${email}...`);
139
218
  } else {
@@ -142,6 +221,9 @@ async function login(credentials = {}) {
142
221
  const serviceName = finalTargetUrl.includes('outlook') ? 'Outlook' : 'OneNote';
143
222
  logger.warn(`The script will wait until you successfully reach the ${serviceName} interface.`);
144
223
  }
224
+
225
+ // Prepare files (backup existing if needed, create directory)
226
+ await checkAndPrepareFiles(filePath);
145
227
 
146
228
  const browser = await chromium.launch({ headless: !!headless });
147
229
  const context = await browser.newContext({
@@ -486,7 +568,7 @@ async function login(credentials = {}) {
486
568
  } catch (e) {
487
569
  logger.debug(`Post-password verification handling skipped or failed: ${e.message}`);
488
570
  }
489
-
571
+
490
572
  // 2.7. Handle "Help protect your account" interrupt screen
491
573
  try {
492
574
  const interruptPrompt = page.getByText(/Help protect your account/i).first();
@@ -565,14 +647,14 @@ async function login(credentials = {}) {
565
647
  }
566
648
 
567
649
  logger.info('Saving authentication state...');
568
- await context.storageState({ path: AUTH_FILE });
650
+ await context.storageState({ path: filePath });
569
651
 
570
- await fs.writeJson(AUTH_META_FILE, {
652
+ await fs.writeJson(metaPath, {
571
653
  email: email || 'manual login',
572
654
  loginTime: new Date().toISOString()
573
655
  });
574
656
 
575
- logger.success(`Authentication successful! State saved to ${AUTH_FILE}`);
657
+ logger.success(`Authentication successful! State saved to ${filePath}`);
576
658
  } catch (error) {
577
659
  logger.error('Authentication failed or cancelled:', error);
578
660
  if (isAutomated) {
@@ -583,16 +665,19 @@ async function login(credentials = {}) {
583
665
  }
584
666
  }
585
667
 
586
- async function getAuthenticatedContext(browser) {
587
- if (await fs.pathExists(AUTH_FILE)) {
588
- return browser.newContext({ storageState: AUTH_FILE });
668
+ async function getAuthenticatedContext(browser, authFilePath) {
669
+ const filePath = getAuthFilePath(authFilePath);
670
+ if (await fs.pathExists(filePath)) {
671
+ return browser.newContext({ storageState: filePath });
589
672
  } else {
590
673
  throw new Error('No authentication state found. Please run "login" command first.');
591
674
  }
592
675
  }
593
676
 
594
- async function checkAuth(targetUrl = ONENOTE_URL) {
595
- if (!(await fs.pathExists(AUTH_FILE))) {
677
+ async function checkAuth(targetUrl = ONENOTE_URL, authFilePath) {
678
+ const filePath = getAuthFilePath(authFilePath);
679
+
680
+ if (!(await fs.pathExists(filePath))) {
596
681
  return false;
597
682
  }
598
683
 
@@ -600,7 +685,7 @@ async function checkAuth(targetUrl = ONENOTE_URL) {
600
685
  try {
601
686
  logger.debug('Verifying authentication session...');
602
687
  browser = await chromium.launch({ headless: true });
603
- const context = await browser.newContext({ storageState: AUTH_FILE });
688
+ const context = await browser.newContext({ storageState: filePath });
604
689
  const page = await context.newPage();
605
690
 
606
691
  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
@@ -613,7 +698,7 @@ async function checkAuth(targetUrl = ONENOTE_URL) {
613
698
 
614
699
  if (isLoginUrl) {
615
700
  logger.warn('Authentication session has expired. Deleting stale auth state.');
616
- await logout();
701
+ await logout(authFilePath);
617
702
  return false;
618
703
  }
619
704
 
package/src/config.js CHANGED
@@ -1,22 +1,49 @@
1
+ /**
2
+ * @fileoverview Returns the directory where auth files (auth.json, auth-meta.json) are stored.
3
+ * @author phptr,enoola,msout
4
+ * @copyright 2026 phptr,enoola,msout
5
+ */
1
6
  const path = require('path');
7
+ const os = require('os');
8
+ const fs = require('fs-extra');
2
9
 
3
10
  /**
4
- * Returns the directory where auth files (auth.json, auth-meta.json) are stored.
5
- * For this standalone CLI tool, we use the project root.
11
+ * Returns the default auth file path (~/.microsoft-webauth/auth-file.json)
6
12
  */
7
- function getUserDataDir() {
8
- 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');
9
17
  }
10
18
 
11
- 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
+ }
12
38
 
13
- const AUTH_FILE = path.join(USER_DATA_DIR, 'auth.json');
39
+ const DEFAULT_AUTH_FILE = getDefaultAuthFilePath();
14
40
  const ONENOTE_URL = 'https://onenote.cloud.microsoft/en-us';
15
41
  const OUTLOOK_URL = 'https://outlook.cloud.microsoft/mail/';
16
42
 
17
43
  module.exports = {
18
- AUTH_FILE,
44
+ DEFAULT_AUTH_FILE,
45
+ getAuthMetaFilePath,
46
+ ensureAuthDir,
19
47
  ONENOTE_URL,
20
48
  OUTLOOK_URL,
21
- USER_DATA_DIR,
22
49
  };
package/src/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview Main CLI interface to handle authentication commands.
4
+ * @author phptr,enoola,msout
5
+ * @copyright 2026 phptr,enoola,msout
6
+ */
2
7
  const { program } = require('commander');
3
8
  const logger = require('./utils/logger');
4
9
  const { login, checkAuth, getAuthMeta, logout } = require('./auth');
5
- const { ONENOTE_URL, OUTLOOK_URL } = require('./config');
10
+ const { DEFAULT_AUTH_FILE, ONENOTE_URL, OUTLOOK_URL } = require('./config');
6
11
 
7
12
  program
8
13
  .name('webauth')
@@ -17,6 +22,7 @@ program
17
22
  .option('--notheadless', 'Run in visible browser mode for debugging')
18
23
  .option('--dodump', 'Dump HTML content to files for debugging')
19
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)
20
26
  .action(async (options) => {
21
27
  const targetUrl = options.against === 'outlook' ? OUTLOOK_URL : ONENOTE_URL;
22
28
  await login({ ...options, targetUrl });
@@ -26,12 +32,13 @@ program
26
32
  .command('check')
27
33
  .description('Check if authenticated')
28
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)
29
36
  .action(async (options) => {
30
37
  const targetUrl = options.against === 'outlook' ? OUTLOOK_URL : ONENOTE_URL;
31
- const isAuth = await checkAuth(targetUrl);
38
+ const isAuth = await checkAuth(targetUrl, options.authFile);
32
39
  if (isAuth) {
33
40
  logger.success('Authentication file found. You are authenticated.');
34
- const meta = await getAuthMeta();
41
+ const meta = await getAuthMeta(options.authFile);
35
42
  if (meta && meta.email) {
36
43
  const loginTime = new Date(meta.loginTime).toLocaleString();
37
44
  logger.info(`Logged in as: ${meta.email}`);
@@ -45,8 +52,9 @@ program
45
52
  program
46
53
  .command('logout')
47
54
  .description('Clear authentication state')
48
- .action(async () => {
49
- 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);
50
58
  logger.success('Logged out successfully. Authentication state cleared.');
51
59
  });
52
60