microsoft-onedrive-mock 1.0.0 → 1.0.1
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 +21 -0
- package/README.md +76 -1
- package/package.json +24 -4
- package/.ENV_EXAMPLE +0 -16
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/release.yml +0 -50
- package/eslint.config.mjs +0 -15
- package/examples/device-login.ts +0 -84
- package/examples/microsoft-login.html +0 -227
- package/examples/serve-login.ts +0 -11
- package/scripts/check-token.ts +0 -70
- package/specs/odata.xml +0 -2
- package/specs/openapi.yaml +0 -196491
- package/src/batch.ts +0 -91
- package/src/index.ts +0 -102
- package/src/routes/v1.ts +0 -227
- package/src/store.ts +0 -191
- package/src/types.ts +0 -59
- package/test/basics.test.ts +0 -119
- package/test/batch.test.ts +0 -75
- package/test/config.ts +0 -63
- package/test/etag.test.ts +0 -69
- package/test/search.test.ts +0 -57
- package/test/select.test.ts +0 -68
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -33
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Meyer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1 +1,76 @@
|
|
|
1
|
-
# microsoft-onedrive-mock
|
|
1
|
+
# microsoft-onedrive-mock
|
|
2
|
+
|
|
3
|
+
<br />
|
|
4
|
+
|
|
5
|
+
<p style="text-align: center;">
|
|
6
|
+
Mock-Server that simulates being Microsoft OneDrive (Graph API).<br />
|
|
7
|
+
Used for testing the <a href="https://rxdb.info/" target="_blank">RxDB OneDrive-Sync</a>.<br />
|
|
8
|
+
Sister project to <a href="https://github.com/pubkey/google-drive-mock" target="_blank">google-drive-mock</a>.<br />
|
|
9
|
+
Mostly Vibe-Coded.<br />
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install microsoft-onedrive-mock
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { startServer } from 'microsoft-onedrive-mock';
|
|
22
|
+
|
|
23
|
+
// start the server
|
|
24
|
+
const port = 3000;
|
|
25
|
+
const server = startServer(port);
|
|
26
|
+
|
|
27
|
+
// Read the drive root
|
|
28
|
+
const readResponse = await fetch(`http://localhost:3000/v1.0/me/drive/root`, {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
headers: {
|
|
31
|
+
'Authorization': 'Bearer valid-token'
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const folderContent = await readResponse.json();
|
|
35
|
+
console.log('Read Root:', folderContent);
|
|
36
|
+
|
|
37
|
+
// Stop the server
|
|
38
|
+
server.close();
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Tech
|
|
43
|
+
|
|
44
|
+
- TypeScript
|
|
45
|
+
- Express
|
|
46
|
+
- Vitest
|
|
47
|
+
|
|
48
|
+
## Browser Testing
|
|
49
|
+
|
|
50
|
+
To run tests inside a headless browser (Chromium):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run test:browser
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Real Microsoft Graph API Testing
|
|
57
|
+
|
|
58
|
+
To run tests against the real Microsoft Graph / OneDrive API instead of the mock:
|
|
59
|
+
|
|
60
|
+
1. Create a `.ENV` file (see `.ENV_EXAMPLE`):
|
|
61
|
+
```
|
|
62
|
+
TEST_TARGET=real
|
|
63
|
+
ONEDRIVE_TOKEN=your-access-token
|
|
64
|
+
```
|
|
65
|
+
2. You can generate a valid `ONEDRIVE_TOKEN` quickly by running the included login script and following the browser prompts:
|
|
66
|
+
```bash
|
|
67
|
+
npm run example:login
|
|
68
|
+
```
|
|
69
|
+
3. Run tests:
|
|
70
|
+
```bash
|
|
71
|
+
npm run test:real
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Contributing
|
|
75
|
+
|
|
76
|
+
GitHub issues for this project are closed. If you find a bug, please create a Pull Request with a test case reproducing the issue.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "microsoft-onedrive-mock",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Mock-Server that simulates being Microsoft OneDrive. Used for testing.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,13 +18,33 @@
|
|
|
18
18
|
"lint": "eslint .",
|
|
19
19
|
"lint:fix": "eslint . --fix"
|
|
20
20
|
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/pubkey/microsoft-onedrive-mock.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"microsoft-onedrive",
|
|
27
|
+
"onedrive",
|
|
28
|
+
"mock",
|
|
29
|
+
"testing",
|
|
30
|
+
"server",
|
|
31
|
+
"api",
|
|
32
|
+
"simulator",
|
|
33
|
+
"rxdb"
|
|
34
|
+
],
|
|
35
|
+
"author": "pubkey",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/pubkey/microsoft-onedrive-mock/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/pubkey/microsoft-onedrive-mock#readme",
|
|
21
41
|
"type": "commonjs",
|
|
22
42
|
"peerDependencies": {
|
|
23
43
|
"express": "5.2.1"
|
|
24
44
|
},
|
|
25
45
|
"devDependencies": {
|
|
26
46
|
"@eslint/js": "9.39.2",
|
|
27
|
-
"@microsoft/microsoft-graph-types": "
|
|
47
|
+
"@microsoft/microsoft-graph-types": "2.43.1",
|
|
28
48
|
"@types/cors": "2.8.19",
|
|
29
49
|
"@types/express": "5.0.6",
|
|
30
50
|
"@types/node": "25.1.0",
|
|
@@ -40,9 +60,9 @@
|
|
|
40
60
|
"playwright": "1.58.0",
|
|
41
61
|
"start-server-and-test": "2.1.3",
|
|
42
62
|
"supertest": "7.2.2",
|
|
43
|
-
"ts-node": "
|
|
63
|
+
"ts-node": "10.9.2",
|
|
44
64
|
"typescript": "5.9.3",
|
|
45
65
|
"typescript-eslint": "8.54.0",
|
|
46
66
|
"vitest": "4.0.18"
|
|
47
67
|
}
|
|
48
|
-
}
|
|
68
|
+
}
|
package/.ENV_EXAMPLE
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# To run tests against the real Microsoft Graph API
|
|
2
|
-
# Copy this file to .ENV and fill in the values
|
|
3
|
-
|
|
4
|
-
# 'mock' (default) or 'real'
|
|
5
|
-
TEST_TARGET=real
|
|
6
|
-
|
|
7
|
-
# OAuth2 Access Token for your Microsoft Account
|
|
8
|
-
# Get one via 'npm run example:login'
|
|
9
|
-
ONEDRIVE_TOKEN=...
|
|
10
|
-
|
|
11
|
-
# Optional: Simulate latency in ms (only works with TEST_TARGET=mock)
|
|
12
|
-
# LATENCY=50
|
|
13
|
-
|
|
14
|
-
# Client ID for the Microsoft Login Example
|
|
15
|
-
ONEDRIVE_CLIENT_ID=e5...
|
|
16
|
-
ONEDRIVE_TENANT_ID=common
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ main, master ]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [ main, master ]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build-and-test:
|
|
11
|
-
runs-on: ubuntu-24.04
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
|
|
16
|
-
- name: Use Node.js
|
|
17
|
-
uses: actions/setup-node@v4
|
|
18
|
-
with:
|
|
19
|
-
node-version: '24'
|
|
20
|
-
cache: 'npm'
|
|
21
|
-
|
|
22
|
-
- name: Install dependencies
|
|
23
|
-
run: npm install
|
|
24
|
-
|
|
25
|
-
- name: Lint
|
|
26
|
-
run: npm run lint
|
|
27
|
-
|
|
28
|
-
- name: Build
|
|
29
|
-
run: npm run build
|
|
30
|
-
|
|
31
|
-
- name: Test (Node)
|
|
32
|
-
run: npm test
|
|
33
|
-
|
|
34
|
-
- name: Install Playwright Browsers
|
|
35
|
-
run: npx playwright@1.58.0 install chromium
|
|
36
|
-
|
|
37
|
-
- name: Test (Browser)
|
|
38
|
-
run: npm run test:browser
|
|
39
|
-
|
|
40
|
-
- name: Test Slow
|
|
41
|
-
run: npm run test:slow
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
inputs:
|
|
6
|
-
version:
|
|
7
|
-
description: 'New Version (e.g. 1.0.0, or leave empty for patch)'
|
|
8
|
-
required: false
|
|
9
|
-
type: string
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build:
|
|
13
|
-
runs-on: ubuntu-24.04
|
|
14
|
-
# @link https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
|
15
|
-
permissions:
|
|
16
|
-
contents: write
|
|
17
|
-
# @link https://docs.npmjs.com/generating-provenance-statements#about-npm-provenance
|
|
18
|
-
id-token: write
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v4
|
|
21
|
-
- uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: '24'
|
|
24
|
-
registry-url: 'https://registry.npmjs.org'
|
|
25
|
-
cache: 'npm'
|
|
26
|
-
|
|
27
|
-
- run: npm install
|
|
28
|
-
- name: Bump Version
|
|
29
|
-
id: bump
|
|
30
|
-
run: |
|
|
31
|
-
if [ -z "${{ inputs.version }}" ]; then
|
|
32
|
-
VERSION=$(npm version patch --no-git-tag-version)
|
|
33
|
-
else
|
|
34
|
-
VERSION=$(npm version ${{ inputs.version }} --no-git-tag-version)
|
|
35
|
-
fi
|
|
36
|
-
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
37
|
-
- run: npm run build
|
|
38
|
-
- run: npm run lint
|
|
39
|
-
- run: npm test
|
|
40
|
-
- run: npm publish
|
|
41
|
-
env:
|
|
42
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
43
|
-
|
|
44
|
-
- name: Push changes
|
|
45
|
-
run: |
|
|
46
|
-
git config --global user.name 'github-actions[bot]'
|
|
47
|
-
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
|
48
|
-
git add package.json package-lock.json
|
|
49
|
-
git commit -m "release: ${{ steps.bump.outputs.version }}"
|
|
50
|
-
git push
|
package/eslint.config.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import globals from "globals";
|
|
2
|
-
import pluginJs from "@eslint/js";
|
|
3
|
-
import tseslint from "typescript-eslint";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** @type {import('eslint').Linter.Config[]} */
|
|
7
|
-
export default [
|
|
8
|
-
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
|
9
|
-
{ languageOptions: { globals: globals.node } },
|
|
10
|
-
pluginJs.configs.recommended,
|
|
11
|
-
...tseslint.configs.recommended,
|
|
12
|
-
{
|
|
13
|
-
ignores: ["dist/**", "coverage/**", "node_modules/**"]
|
|
14
|
-
}
|
|
15
|
-
];
|
package/examples/device-login.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
|
|
4
|
-
const clientId = 'e5f346a8-8996-4d46-9f93-6e6817d9078e';
|
|
5
|
-
const tenantId = 'common';
|
|
6
|
-
|
|
7
|
-
async function runDeviceLogin() {
|
|
8
|
-
const authority = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0`;
|
|
9
|
-
|
|
10
|
-
// 1. Get device code
|
|
11
|
-
const deviceCodeParams = new URLSearchParams();
|
|
12
|
-
deviceCodeParams.append('client_id', clientId);
|
|
13
|
-
deviceCodeParams.append('scope', 'Files.ReadWrite.All User.Read offline_access');
|
|
14
|
-
|
|
15
|
-
let deviceCodeRes;
|
|
16
|
-
try {
|
|
17
|
-
deviceCodeRes = await fetch(`${authority}/devicecode`, {
|
|
18
|
-
method: 'POST',
|
|
19
|
-
headers: {
|
|
20
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
21
|
-
},
|
|
22
|
-
body: deviceCodeParams.toString()
|
|
23
|
-
});
|
|
24
|
-
} catch (e) {
|
|
25
|
-
console.error("Network error executing fetch request:", e);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const deviceCodeData = await deviceCodeRes.json();
|
|
30
|
-
if (!deviceCodeRes.ok) {
|
|
31
|
-
console.error('❌ Failed to start device login:', deviceCodeData);
|
|
32
|
-
console.error('\nNOTE: Your Azure App must have "Allow public client flows" enabled in Authentication settings for Device Code flow to work.');
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log('\n====================================================');
|
|
37
|
-
console.log(deviceCodeData.message);
|
|
38
|
-
console.log('====================================================\n');
|
|
39
|
-
|
|
40
|
-
// 2. Poll for token
|
|
41
|
-
const tokenParams = new URLSearchParams();
|
|
42
|
-
tokenParams.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
|
|
43
|
-
tokenParams.append('client_id', clientId);
|
|
44
|
-
tokenParams.append('device_code', deviceCodeData.device_code);
|
|
45
|
-
|
|
46
|
-
let polling = true;
|
|
47
|
-
while (polling) {
|
|
48
|
-
await new Promise(resolve => setTimeout(resolve, deviceCodeData.interval * 1000));
|
|
49
|
-
|
|
50
|
-
const tokenRes = await fetch(`${authority}/token`, {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
headers: {
|
|
53
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
54
|
-
},
|
|
55
|
-
body: tokenParams.toString()
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const tokenData = await tokenRes.json();
|
|
59
|
-
|
|
60
|
-
if (tokenRes.ok) {
|
|
61
|
-
console.log('\n✅ Successfully authenticated!');
|
|
62
|
-
|
|
63
|
-
// Save to .ENV
|
|
64
|
-
const envPath = path.resolve(__dirname, '../.ENV');
|
|
65
|
-
let envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
|
|
66
|
-
if (envContent.includes('ONEDRIVE_TOKEN=')) {
|
|
67
|
-
envContent = envContent.replace(/ONEDRIVE_TOKEN=[^\n]*/, `ONEDRIVE_TOKEN=${tokenData.access_token}`);
|
|
68
|
-
} else {
|
|
69
|
-
envContent += `\nONEDRIVE_TOKEN=${tokenData.access_token}\n`;
|
|
70
|
-
}
|
|
71
|
-
fs.writeFileSync(envPath, envContent);
|
|
72
|
-
console.log('✅ Updated .ENV with your new ONEDRIVE_TOKEN');
|
|
73
|
-
console.log('You can now run: npm run test:real');
|
|
74
|
-
polling = false;
|
|
75
|
-
} else if (tokenData.error !== 'authorization_pending') {
|
|
76
|
-
console.error('\n❌ Login failed:', tokenData.error_description || tokenData.error);
|
|
77
|
-
polling = false;
|
|
78
|
-
} else {
|
|
79
|
-
process.stdout.write('.');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
runDeviceLogin();
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Microsoft OneDrive Token Generator</title>
|
|
8
|
-
<style>
|
|
9
|
-
body {
|
|
10
|
-
font-family: sans-serif;
|
|
11
|
-
padding: 20px;
|
|
12
|
-
max-width: 600px;
|
|
13
|
-
margin: 0 auto;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.form-group {
|
|
17
|
-
margin-bottom: 15px;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
label {
|
|
21
|
-
display: block;
|
|
22
|
-
margin-bottom: 5px;
|
|
23
|
-
font-weight: bold;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
input[type="text"] {
|
|
27
|
-
width: 100%;
|
|
28
|
-
padding: 8px;
|
|
29
|
-
box-sizing: border-box;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
button {
|
|
33
|
-
padding: 10px 20px;
|
|
34
|
-
background-color: #0078d4;
|
|
35
|
-
color: white;
|
|
36
|
-
border: none;
|
|
37
|
-
cursor: pointer;
|
|
38
|
-
font-size: 16px;
|
|
39
|
-
border-radius: 4px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
button:hover {
|
|
43
|
-
background-color: #005a9e;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
button#copy-btn {
|
|
47
|
-
background-color: #28a745;
|
|
48
|
-
margin-top: 10px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
button#copy-btn:hover {
|
|
52
|
-
background-color: #218838;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
#token-display {
|
|
56
|
-
margin-top: 20px;
|
|
57
|
-
background: #f0f0f0;
|
|
58
|
-
padding: 15px;
|
|
59
|
-
border-radius: 4px;
|
|
60
|
-
display: none;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
textarea {
|
|
64
|
-
width: 100%;
|
|
65
|
-
padding: 8px;
|
|
66
|
-
box-sizing: border-box;
|
|
67
|
-
border: 1px solid #ccc;
|
|
68
|
-
border-radius: 4px;
|
|
69
|
-
resize: vertical;
|
|
70
|
-
font-family: monospace;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.hidden {
|
|
74
|
-
display: none;
|
|
75
|
-
}
|
|
76
|
-
</style>
|
|
77
|
-
<!-- MSAL.js v3 -->
|
|
78
|
-
<script type="text/javascript" src="https://alcdn.msauth.net/browser/2.37.1/js/msal-browser.min.js"></script>
|
|
79
|
-
</head>
|
|
80
|
-
|
|
81
|
-
<body>
|
|
82
|
-
|
|
83
|
-
<h1>Microsoft OneDrive Login</h1>
|
|
84
|
-
<p>Enter your Client ID (Application ID) and Tenant ID to authenticate and get an access token. The login uses
|
|
85
|
-
MSAL.js for OAuth2 Implicit/SPA flow.</p>
|
|
86
|
-
|
|
87
|
-
<div class="form-group">
|
|
88
|
-
<label for="client-id">Client ID:</label>
|
|
89
|
-
<input type="text" id="client-id" placeholder="e.g. 11111111-1111-1111-1111-111111111111">
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div class="form-group">
|
|
93
|
-
<label for="tenant-id">Tenant ID:</label>
|
|
94
|
-
<input type="text" id="tenant-id" placeholder="e.g. common (or your specific tenant GUID)">
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<button id="login-btn" onclick="handleAuth()">Login to Microsoft Graph</button>
|
|
98
|
-
|
|
99
|
-
<div id="token-display">
|
|
100
|
-
<h3>Access Token:</h3>
|
|
101
|
-
<textarea id="access-token" readonly rows="6"></textarea>
|
|
102
|
-
<button id="copy-btn" onclick="copyToken()">Copy Token</button>
|
|
103
|
-
|
|
104
|
-
<h3>Full .ENV Content:</h3>
|
|
105
|
-
<textarea id="env-content" readonly rows="12"></textarea>
|
|
106
|
-
<button id="copy-env-btn" onclick="copyEnv()">Copy .ENV</button>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<script>
|
|
110
|
-
let msalInstance = null;
|
|
111
|
-
let loginRequest = {
|
|
112
|
-
scopes: ["Files.ReadWrite.All", "User.Read"]
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
window.onload = async function () {
|
|
116
|
-
const storedClientId = localStorage.getItem('ms_client_id');
|
|
117
|
-
const storedTenantId = localStorage.getItem('ms_tenant_id') || 'common';
|
|
118
|
-
if (storedClientId) document.getElementById('client-id').value = storedClientId;
|
|
119
|
-
document.getElementById('tenant-id').value = storedTenantId;
|
|
120
|
-
|
|
121
|
-
// If we have clientId, we can initialize MSAL to check if we are returning from a redirect
|
|
122
|
-
if (storedClientId) {
|
|
123
|
-
initializeMsal(storedClientId, storedTenantId);
|
|
124
|
-
try {
|
|
125
|
-
const response = await msalInstance.handleRedirectPromise();
|
|
126
|
-
if (response && response.accessToken) {
|
|
127
|
-
displayToken(response.accessToken, storedClientId, storedTenantId);
|
|
128
|
-
}
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.error("Redirect error:", err);
|
|
131
|
-
alert("Failed to process login redirect: " + err.message);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function initializeMsal(clientId, tenantId) {
|
|
137
|
-
// Determine if the user is running on https or http
|
|
138
|
-
const currentOrigin = window.location.origin;
|
|
139
|
-
const redirectUri = currentOrigin + "/microsoft-login.html";
|
|
140
|
-
|
|
141
|
-
const msalConfig = {
|
|
142
|
-
auth: {
|
|
143
|
-
clientId: clientId,
|
|
144
|
-
authority: `https://login.microsoftonline.com/${tenantId}`,
|
|
145
|
-
redirectUri: redirectUri,
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
msalInstance = new msal.PublicClientApplication(msalConfig);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function handleAuth() {
|
|
152
|
-
const clientId = document.getElementById('client-id').value.trim();
|
|
153
|
-
const tenantId = document.getElementById('tenant-id').value.trim() || 'common';
|
|
154
|
-
|
|
155
|
-
if (!clientId) {
|
|
156
|
-
alert('Please enter a Client ID');
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
localStorage.setItem('ms_client_id', clientId);
|
|
161
|
-
localStorage.setItem('ms_tenant_id', tenantId);
|
|
162
|
-
|
|
163
|
-
initializeMsal(clientId, tenantId);
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
// Use redirect flow instead of popup. This avoids popup blockers and
|
|
167
|
-
// cleanly returns the user to this page with the '#code=...' in the URL,
|
|
168
|
-
// which handleRedirectPromise() will then automatically exchange for the JWT Access Token!
|
|
169
|
-
await msalInstance.loginRedirect(loginRequest);
|
|
170
|
-
} catch (err) {
|
|
171
|
-
console.error(err);
|
|
172
|
-
alert('Login failed: ' + (err.message || err));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function displayToken(accessToken, clientId, tenantId) {
|
|
177
|
-
document.getElementById('token-display').style.display = 'block';
|
|
178
|
-
document.getElementById('access-token').value = accessToken;
|
|
179
|
-
|
|
180
|
-
const envContent = `# To run tests against the real Microsoft Graph API
|
|
181
|
-
# Copy this file to .ENV and fill in the values
|
|
182
|
-
|
|
183
|
-
# 'mock' (default) or 'real'
|
|
184
|
-
TEST_TARGET=real
|
|
185
|
-
|
|
186
|
-
# OAuth2 Access Token for your Microsoft Account
|
|
187
|
-
# Get one via 'npm run example:login'
|
|
188
|
-
ONEDRIVE_TOKEN=${accessToken}
|
|
189
|
-
|
|
190
|
-
# Client ID for the Microsoft Login Example
|
|
191
|
-
ONEDRIVE_CLIENT_ID=${clientId}
|
|
192
|
-
ONEDRIVE_TENANT_ID=${tenantId}
|
|
193
|
-
|
|
194
|
-
# Optional: Simulate latency in ms (only works with TEST_TARGET=mock)
|
|
195
|
-
# LATENCY=50
|
|
196
|
-
`;
|
|
197
|
-
document.getElementById('env-content').value = envContent;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function copyToken() {
|
|
201
|
-
copyToClipboard('access-token', 'copy-btn');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function copyEnv() {
|
|
205
|
-
copyToClipboard('env-content', 'copy-env-btn');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function copyToClipboard(elementId, buttonId) {
|
|
209
|
-
const textEl = document.getElementById(elementId);
|
|
210
|
-
textEl.select();
|
|
211
|
-
textEl.setSelectionRange(0, 99999);
|
|
212
|
-
|
|
213
|
-
navigator.clipboard.writeText(textEl.value).then(() => {
|
|
214
|
-
const btn = document.getElementById(buttonId);
|
|
215
|
-
const originalText = btn.innerText;
|
|
216
|
-
btn.innerText = 'Copied!';
|
|
217
|
-
setTimeout(() => {
|
|
218
|
-
btn.innerText = originalText;
|
|
219
|
-
}, 2000);
|
|
220
|
-
}).catch(err => {
|
|
221
|
-
console.error('Failed to copy text: ', err);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
</script>
|
|
225
|
-
</body>
|
|
226
|
-
|
|
227
|
-
</html>
|
package/examples/serve-login.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
|
|
3
|
-
const app = express();
|
|
4
|
-
const port = 8080;
|
|
5
|
-
|
|
6
|
-
app.use(express.static(__dirname));
|
|
7
|
-
|
|
8
|
-
app.listen(port, () => {
|
|
9
|
-
console.log(`Login example running at http://localhost:${port}/microsoft-login.html`);
|
|
10
|
-
console.log('NOTE: Ensure "http://localhost:8080/microsoft-login.html" is added as a Redirect URI in your Microsoft Entra ID app registration (SPA).');
|
|
11
|
-
});
|
package/scripts/check-token.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as https from 'https';
|
|
4
|
-
|
|
5
|
-
const envPath = path.resolve(__dirname, '../.ENV');
|
|
6
|
-
if (fs.existsSync(envPath)) {
|
|
7
|
-
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
8
|
-
envContent.split('\n').forEach(line => {
|
|
9
|
-
const match = line.match(/^([^=]+)=(.*)$/);
|
|
10
|
-
if (match) {
|
|
11
|
-
const key = match[1].trim();
|
|
12
|
-
const value = match[2].trim().replace(/^['"]|['"]$/g, '');
|
|
13
|
-
if (!process.env[key]) {
|
|
14
|
-
process.env[key] = value;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const token = process.env.ONEDRIVE_TOKEN || process.env.GRAPH_TOKEN;
|
|
21
|
-
|
|
22
|
-
if (!token) {
|
|
23
|
-
console.error('❌ Error: ONEDRIVE_TOKEN or GRAPH_TOKEN not found in environment or .ENV file.');
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.log('🔄 Verifying Microsoft Graph Token...');
|
|
28
|
-
|
|
29
|
-
const options = {
|
|
30
|
-
hostname: 'graph.microsoft.com',
|
|
31
|
-
path: '/v1.0/me',
|
|
32
|
-
method: 'GET',
|
|
33
|
-
headers: {
|
|
34
|
-
'Authorization': `Bearer ${token}`,
|
|
35
|
-
'User-Agent': 'node-script'
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const req = https.request(options, (res) => {
|
|
40
|
-
let data = '';
|
|
41
|
-
|
|
42
|
-
res.on('data', (chunk) => {
|
|
43
|
-
data += chunk;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
res.on('end', () => {
|
|
47
|
-
if (res.statusCode === 200) {
|
|
48
|
-
try {
|
|
49
|
-
const body = JSON.parse(data);
|
|
50
|
-
console.log(`✅ Token is valid. User: ${body.userPrincipalName || body.displayName || 'Unknown'}`);
|
|
51
|
-
process.exit(0);
|
|
52
|
-
} catch (e: unknown) {
|
|
53
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
54
|
-
console.error('❌ Error parsing response:', msg);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
console.error(`❌ Token verification failed. Update .ENV file with a valid token. Status: ${res.statusCode}`);
|
|
59
|
-
console.error('Response:', data);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
req.on('error', (e) => {
|
|
66
|
-
console.error(`❌ Request error: ${e.message}`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
req.end();
|