-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmsauthify.js
More file actions
126 lines (108 loc) · 3.87 KB
/
Copy pathmsauthify.js
File metadata and controls
126 lines (108 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env node --no-warnings=ExperimentalWarning
import { readFileSync } from 'fs';
import os from 'os';
import { Command } from 'commander';
import clipboard from 'clipboardy';
import pkg from './package.json' with {type: 'json'};
import { checkUpdate } from './update-notifier.js';
import axios from 'axios';
const CONFIG_PATH = `${os.homedir()}/msauthify.config`;
const updateAvailable = await checkUpdate({
author: pkg.author,
repository: pkg.repository.name,
name: pkg.name,
version: pkg.version
})
if (updateAvailable) { process.exit(); }
const program = new Command();
program
.name('msauthify')
.description(pkg.description)
.usage('[options] <profile>')
.version(pkg.version, '-v, --version', 'Show version')
.helpOption('-h, --help', 'Show help')
.option('-l, --list', 'List available profiles from msauthify.config')
.option('-d, --decode', 'Decode the JWT and output its header and payload as JSON')
.option('-c, --copy', 'Copy the token to the system clipboard')
.argument('[profile]', 'Profile name defined in msauthify.config')
.showHelpAfterError('(use --help for more info, or --list to see available profiles)')
.action(async (profile, opts) => {
const config = loadConfig();
if (opts.list) {
listProfiles(config);
return;
}
if (!profile) {
program.error("error: missing required argument 'profile'");
}
await run(profile, config, opts);
});
program.parseAsync().catch((error) => {
console.error(error.message);
process.exit(1);
});
function listProfiles(config) {
const profiles = Object.keys(config);
for (const name of profiles) {
console.log(name);
}
}
async function run(profile, config, opts) {
validateProfile(profile, config);
const token = await fetchToken(config[profile]);
if (opts.copy) {
const payload = opts.decode
? JSON.stringify(decodeJwt(token), null, 2)
: token;
await clipboard.write(payload);
console.error(`Token for '${profile}' copied to clipboard`);
return;
}
if (opts.decode) {
console.log(JSON.stringify(decodeJwt(token), null, 2));
return;
}
console.log(token);
}
function decodeJwt(token) {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT: expected 3 dot-separated segments');
}
const [headerB64, payloadB64] = parts;
const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8'));
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
return { header, payload };
}
function validateProfile(profile, config) {
if (!Object.prototype.hasOwnProperty.call(config, profile)) {
throw new Error(`Invalid config: '${profile}' not found in ${CONFIG_PATH}`);
}
}
function loadConfig() {
const data = readFileSync(CONFIG_PATH, 'utf8');
return JSON.parse(data);
}
async function fetchToken(config) {
const url = `https://login.microsoftonline.com/${config.tenantId}/oauth2/v2.0/token`;
const payload = new URLSearchParams({
client_id: config.clientId,
client_secret: config.clientSecret,
scope: config.scope,
username: config.username,
password: config.password,
grant_type: "password",
});
try {
return (await axios.post(url, payload)).data.access_token;
} catch (error) {
const data = error.response?.data;
if (data?.error_description || data?.error) {
const code = data.error ?? 'unknown_error';
const description = data.error_description ?? 'No description provided';
const correlationId = data.correlation_id ?? 'N/A';
throw new Error(`[${code}] ${description} (correlation_id: ${correlationId})`);
}
throw error;
}
}