@ktmcp-cli/authentiq-api 1.0.0
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 +82 -0
- package/bin/authentiq-api.js +56 -0
- package/package.json +33 -0
- package/src/commands/config.js +42 -0
- package/src/commands/key.js +332 -0
- package/src/commands/login.js +62 -0
- package/src/commands/scope.js +239 -0
- package/src/lib/api.js +59 -0
- package/src/lib/config.js +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Authentiq API CLI
|
|
2
|
+
|
|
3
|
+
Production-ready command-line interface for the Authentiq API API.
|
|
4
|
+
|
|
5
|
+
> **⚠️ Unofficial CLI** - This tool is an independent project built on the public API.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Complete coverage of Authentiq API API endpoints
|
|
10
|
+
- Simple, intuitive command structure
|
|
11
|
+
- JSON and pretty-print output formats
|
|
12
|
+
- Persistent configuration storage
|
|
13
|
+
- Progress indicators for async operations
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @ktmcp-cli/authentiq-api
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install locally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd authentiq-api
|
|
25
|
+
npm install
|
|
26
|
+
npm link
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
### Set API Key
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
authentiq-api config set apiKey YOUR_KEY_HERE
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Environment Variables
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export API_KEY=your_key_here
|
|
41
|
+
export API_BASE_URL=https://6-dot-authentiqio.appspot.com
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### View Configuration
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
authentiq-api config list
|
|
48
|
+
authentiq-api config get apiKey
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### key
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# List items
|
|
57
|
+
authentiq-api key list
|
|
58
|
+
|
|
59
|
+
# Get item by ID
|
|
60
|
+
authentiq-api key get <id>
|
|
61
|
+
|
|
62
|
+
# Output as JSON
|
|
63
|
+
authentiq-api key list --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Available Resources
|
|
67
|
+
|
|
68
|
+
- `key`
|
|
69
|
+
- `login`
|
|
70
|
+
- `scope`
|
|
71
|
+
|
|
72
|
+
## JSON Output
|
|
73
|
+
|
|
74
|
+
All commands support `--json` flag for machine-readable output:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
authentiq-api key list --json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
import config from '../src/commands/config.js';
|
|
10
|
+
import key from '../src/commands/key.js';
|
|
11
|
+
import login from '../src/commands/login.js';
|
|
12
|
+
import scope from '../src/commands/scope.js';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
|
|
17
|
+
const packageJson = JSON.parse(
|
|
18
|
+
readFileSync(join(__dirname, '../package.json'), 'utf-8')
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const program = new Command();
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('authentiq-api')
|
|
25
|
+
.description(chalk.cyan('Strong authentication, without the passwords.'))
|
|
26
|
+
.version(packageJson.version, '-v, --version', 'Output the current version')
|
|
27
|
+
.addHelpText('after', `
|
|
28
|
+
${chalk.bold('Examples:')}
|
|
29
|
+
$ authentiq-api config set apiKey YOUR_KEY
|
|
30
|
+
$ authentiq-api key list
|
|
31
|
+
$ authentiq-api key list --json
|
|
32
|
+
|
|
33
|
+
${chalk.bold('Environment Variables:')}
|
|
34
|
+
API_KEY API authentication key
|
|
35
|
+
API_BASE_URL API base URL
|
|
36
|
+
API_TIMEOUT Request timeout in ms (default: 30000)
|
|
37
|
+
|
|
38
|
+
${chalk.bold('Documentation:')}
|
|
39
|
+
GitHub: ${chalk.blue('https://github.com/ktmcp-cli/authentiq-api')}
|
|
40
|
+
`);
|
|
41
|
+
|
|
42
|
+
program.addCommand(config);
|
|
43
|
+
program.addCommand(key);
|
|
44
|
+
program.addCommand(login);
|
|
45
|
+
program.addCommand(scope);
|
|
46
|
+
|
|
47
|
+
process.on('unhandledRejection', (error) => {
|
|
48
|
+
console.error(chalk.red('Unhandled error:'), error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
program.parse(process.argv);
|
|
53
|
+
|
|
54
|
+
if (!process.argv.slice(2).length) {
|
|
55
|
+
program.outputHelp();
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/authentiq-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Strong authentication, without the passwords.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "bin/authentiq-api.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"authentiq-api": "./bin/authentiq-api.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/authentiq-api.js",
|
|
12
|
+
"dev": "node bin/authentiq-api.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cli",
|
|
16
|
+
"api",
|
|
17
|
+
"authentiq-api",
|
|
18
|
+
"ktmcp"
|
|
19
|
+
],
|
|
20
|
+
"author": "KTMCP",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"commander": "^12.0.0",
|
|
24
|
+
"axios": "^1.6.0",
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"conf": "^12.0.0",
|
|
27
|
+
"dotenv": "^16.4.0",
|
|
28
|
+
"ora": "^8.0.1"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { loadConfig, setConfig, getConfig, saveConfig } from '../lib/config.js';
|
|
4
|
+
|
|
5
|
+
const config = new Command('config');
|
|
6
|
+
|
|
7
|
+
config.description('Manage CLI configuration');
|
|
8
|
+
|
|
9
|
+
config
|
|
10
|
+
.command('set <key> <value>')
|
|
11
|
+
.description('Set a configuration value')
|
|
12
|
+
.action((key, value) => {
|
|
13
|
+
if (setConfig(key, value)) {
|
|
14
|
+
console.log(chalk.green(`✓ Set ${key} = ${value}`));
|
|
15
|
+
} else {
|
|
16
|
+
console.error(chalk.red('Failed to save configuration'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
config
|
|
22
|
+
.command('get <key>')
|
|
23
|
+
.description('Get a configuration value')
|
|
24
|
+
.action((key) => {
|
|
25
|
+
const value = getConfig(key);
|
|
26
|
+
if (value !== undefined) {
|
|
27
|
+
console.log(chalk.cyan(`${key} = ${value}`));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.yellow(`${key} is not set`));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
config
|
|
34
|
+
.command('list')
|
|
35
|
+
.description('List all configuration values')
|
|
36
|
+
.action(() => {
|
|
37
|
+
const all = loadConfig();
|
|
38
|
+
console.log(chalk.cyan('Current configuration:'));
|
|
39
|
+
console.log(JSON.stringify(all, null, 2));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default config;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { createClient, handleError } from '../lib/api.js';
|
|
5
|
+
|
|
6
|
+
const key = new Command('key');
|
|
7
|
+
|
|
8
|
+
key
|
|
9
|
+
.description('Manage key');
|
|
10
|
+
|
|
11
|
+
key
|
|
12
|
+
.command('delete')
|
|
13
|
+
.description('Revoke an Authentiq ID using email & phone.
|
|
14
|
+
|
|
15
|
+
If called with `email` and `phone` only, a verification code
|
|
16
|
+
will be sent by email. Do a second call adding `code` to
|
|
17
|
+
complete the revocation.
|
|
18
|
+
')
|
|
19
|
+
.requiredOption('--email <value>', 'primary email associated to Key (ID)')
|
|
20
|
+
.requiredOption('--phone <value>', 'primary phone number, international representation')
|
|
21
|
+
.option('--code <value>', 'verification code sent by email')
|
|
22
|
+
.option('--json', 'Output as JSON')
|
|
23
|
+
.option('-v, --verbose', 'Verbose output')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
const spinner = ora('Revoke an Authentiq ID using email & phone.
|
|
26
|
+
|
|
27
|
+
If called with `email` and `phone` only, a verification code
|
|
28
|
+
will be sent by email. Do a second call adding `code` to
|
|
29
|
+
complete the revocation.
|
|
30
|
+
...').start();
|
|
31
|
+
try {
|
|
32
|
+
const client = createClient();
|
|
33
|
+
const queryParams = {};
|
|
34
|
+
if (options.email) queryParams.email = options.email;
|
|
35
|
+
if (options.phone) queryParams.phone = options.phone;
|
|
36
|
+
if (options.code) queryParams.code = options.code;
|
|
37
|
+
|
|
38
|
+
const response = await client.delete(`/key`);
|
|
39
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
40
|
+
|
|
41
|
+
spinner.succeed('Success');
|
|
42
|
+
|
|
43
|
+
if (result.success) {
|
|
44
|
+
if (options.json) {
|
|
45
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.green('✓ Deleted successfully'));
|
|
48
|
+
}
|
|
49
|
+
process.exit(0);
|
|
50
|
+
} else {
|
|
51
|
+
spinner.fail(`Failed (${result.status})`);
|
|
52
|
+
console.error(chalk.red(result.message));
|
|
53
|
+
if (options.verbose && result.data) {
|
|
54
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
55
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
spinner.fail('Error occurred');
|
|
61
|
+
console.error(chalk.red('Error:'), error.message);
|
|
62
|
+
if (options.verbose) {
|
|
63
|
+
console.error(error.stack);
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
key
|
|
70
|
+
.command('create')
|
|
71
|
+
.description('Register a new ID `JWT(sub, devtoken)`
|
|
72
|
+
|
|
73
|
+
v5: `JWT(sub, pk, devtoken, ...)`
|
|
74
|
+
|
|
75
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
76
|
+
')
|
|
77
|
+
.option('--json', 'Output as JSON')
|
|
78
|
+
.option('-v, --verbose', 'Verbose output')
|
|
79
|
+
.action(async (options) => {
|
|
80
|
+
const spinner = ora('Register a new ID `JWT(sub, devtoken)`
|
|
81
|
+
|
|
82
|
+
v5: `JWT(sub, pk, devtoken, ...)`
|
|
83
|
+
|
|
84
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
85
|
+
...').start();
|
|
86
|
+
try {
|
|
87
|
+
const client = createClient();
|
|
88
|
+
const response = await client.post(`/key`);
|
|
89
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
90
|
+
|
|
91
|
+
spinner.succeed('Success');
|
|
92
|
+
|
|
93
|
+
if (result.success) {
|
|
94
|
+
if (options.json) {
|
|
95
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
96
|
+
} else {
|
|
97
|
+
// Create output
|
|
98
|
+
const item = result.data;
|
|
99
|
+
console.log(chalk.green('✓ Created successfully!'));
|
|
100
|
+
if (item.id) console.log('ID:', chalk.cyan(item.id));
|
|
101
|
+
}
|
|
102
|
+
process.exit(0);
|
|
103
|
+
} else {
|
|
104
|
+
spinner.fail(`Failed (${result.status})`);
|
|
105
|
+
console.error(chalk.red(result.message));
|
|
106
|
+
if (options.verbose && result.data) {
|
|
107
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
108
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
109
|
+
}
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
spinner.fail('Error occurred');
|
|
114
|
+
console.error(chalk.red('Error:'), error.message);
|
|
115
|
+
if (options.verbose) {
|
|
116
|
+
console.error(error.stack);
|
|
117
|
+
}
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
key
|
|
123
|
+
.command('delete <pk>')
|
|
124
|
+
.description('Revoke an Identity (Key) with a revocation secret')
|
|
125
|
+
.requiredOption('--secret <value>', 'revokation secret')
|
|
126
|
+
.option('--json', 'Output as JSON')
|
|
127
|
+
.option('-v, --verbose', 'Verbose output')
|
|
128
|
+
.action(async (pK, options) => {
|
|
129
|
+
const spinner = ora('Revoke an Identity (Key) with a revocation secret...').start();
|
|
130
|
+
try {
|
|
131
|
+
const client = createClient();
|
|
132
|
+
const queryParams = {};
|
|
133
|
+
if (options.secret) queryParams.secret = options.secret;
|
|
134
|
+
|
|
135
|
+
const response = await client.delete(`/key/${pK}`);
|
|
136
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
137
|
+
|
|
138
|
+
spinner.succeed('Success');
|
|
139
|
+
|
|
140
|
+
if (result.success) {
|
|
141
|
+
if (options.json) {
|
|
142
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.green('✓ Deleted successfully'));
|
|
145
|
+
}
|
|
146
|
+
process.exit(0);
|
|
147
|
+
} else {
|
|
148
|
+
spinner.fail(`Failed (${result.status})`);
|
|
149
|
+
console.error(chalk.red(result.message));
|
|
150
|
+
if (options.verbose && result.data) {
|
|
151
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
152
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
153
|
+
}
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
spinner.fail('Error occurred');
|
|
158
|
+
console.error(chalk.red('Error:'), error.message);
|
|
159
|
+
if (options.verbose) {
|
|
160
|
+
console.error(error.stack);
|
|
161
|
+
}
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
key
|
|
167
|
+
.command('get <pk>')
|
|
168
|
+
.description('Get public details of an Authentiq ID.
|
|
169
|
+
')
|
|
170
|
+
.option('--json', 'Output as JSON')
|
|
171
|
+
.option('-v, --verbose', 'Verbose output')
|
|
172
|
+
.action(async (pK, options) => {
|
|
173
|
+
const spinner = ora('Get public details of an Authentiq ID.
|
|
174
|
+
...').start();
|
|
175
|
+
try {
|
|
176
|
+
const client = createClient();
|
|
177
|
+
const response = await client.get(`/key/${pK}`);
|
|
178
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
179
|
+
|
|
180
|
+
spinner.succeed('Success');
|
|
181
|
+
|
|
182
|
+
if (result.success) {
|
|
183
|
+
if (options.json) {
|
|
184
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
185
|
+
} else {
|
|
186
|
+
// Detail output
|
|
187
|
+
const item = result.data;
|
|
188
|
+
console.log(chalk.bold('Details:'));
|
|
189
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
190
|
+
Object.entries(item).slice(0, 10).forEach(([key, value]) => {
|
|
191
|
+
console.log(chalk.cyan(key + ':'), String(value).slice(0, 100));
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
process.exit(0);
|
|
195
|
+
} else {
|
|
196
|
+
spinner.fail(`Failed (${result.status})`);
|
|
197
|
+
console.error(chalk.red(result.message));
|
|
198
|
+
if (options.verbose && result.data) {
|
|
199
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
200
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
201
|
+
}
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
spinner.fail('Error occurred');
|
|
206
|
+
console.error(chalk.red('Error:'), error.message);
|
|
207
|
+
if (options.verbose) {
|
|
208
|
+
console.error(error.stack);
|
|
209
|
+
}
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
key
|
|
215
|
+
.command('update <pk>')
|
|
216
|
+
.description('update properties of an Authentiq ID.
|
|
217
|
+
(not operational in v4; use PUT for now)
|
|
218
|
+
|
|
219
|
+
v5: POST issuer-signed email & phone scopes in
|
|
220
|
+
a self-signed JWT
|
|
221
|
+
|
|
222
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
223
|
+
')
|
|
224
|
+
.option('--json', 'Output as JSON')
|
|
225
|
+
.option('-v, --verbose', 'Verbose output')
|
|
226
|
+
.action(async (pK, options) => {
|
|
227
|
+
const spinner = ora('update properties of an Authentiq ID.
|
|
228
|
+
(not operational in v4; use PUT for now)
|
|
229
|
+
|
|
230
|
+
v5: POST issuer-signed email & phone scopes in
|
|
231
|
+
a self-signed JWT
|
|
232
|
+
|
|
233
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
234
|
+
...').start();
|
|
235
|
+
try {
|
|
236
|
+
const client = createClient();
|
|
237
|
+
const response = await client.post(`/key/${pK}`);
|
|
238
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
239
|
+
|
|
240
|
+
spinner.succeed('Success');
|
|
241
|
+
|
|
242
|
+
if (result.success) {
|
|
243
|
+
if (options.json) {
|
|
244
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
245
|
+
} else {
|
|
246
|
+
// Update/other output
|
|
247
|
+
console.log(chalk.green('✓ Operation completed successfully'));
|
|
248
|
+
if (options.verbose) {
|
|
249
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
process.exit(0);
|
|
253
|
+
} else {
|
|
254
|
+
spinner.fail(`Failed (${result.status})`);
|
|
255
|
+
console.error(chalk.red(result.message));
|
|
256
|
+
if (options.verbose && result.data) {
|
|
257
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
258
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
259
|
+
}
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
spinner.fail('Error occurred');
|
|
264
|
+
console.error(chalk.red('Error:'), error.message);
|
|
265
|
+
if (options.verbose) {
|
|
266
|
+
console.error(error.stack);
|
|
267
|
+
}
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
key
|
|
273
|
+
.command('update <pk>')
|
|
274
|
+
.description('Update Authentiq ID by replacing the object.
|
|
275
|
+
|
|
276
|
+
v4: `JWT(sub,email,phone)` to bind email/phone hash;
|
|
277
|
+
|
|
278
|
+
v5: POST issuer-signed email & phone scopes
|
|
279
|
+
and PUT to update registration `JWT(sub, pk, devtoken, ...)`
|
|
280
|
+
|
|
281
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
282
|
+
')
|
|
283
|
+
.option('--json', 'Output as JSON')
|
|
284
|
+
.option('-v, --verbose', 'Verbose output')
|
|
285
|
+
.action(async (pK, options) => {
|
|
286
|
+
const spinner = ora('Update Authentiq ID by replacing the object.
|
|
287
|
+
|
|
288
|
+
v4: `JWT(sub,email,phone)` to bind email/phone hash;
|
|
289
|
+
|
|
290
|
+
v5: POST issuer-signed email & phone scopes
|
|
291
|
+
and PUT to update registration `JWT(sub, pk, devtoken, ...)`
|
|
292
|
+
|
|
293
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
294
|
+
...').start();
|
|
295
|
+
try {
|
|
296
|
+
const client = createClient();
|
|
297
|
+
const response = await client.put(`/key/${pK}`);
|
|
298
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
299
|
+
|
|
300
|
+
spinner.succeed('Success');
|
|
301
|
+
|
|
302
|
+
if (result.success) {
|
|
303
|
+
if (options.json) {
|
|
304
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
305
|
+
} else {
|
|
306
|
+
// Update/other output
|
|
307
|
+
console.log(chalk.green('✓ Operation completed successfully'));
|
|
308
|
+
if (options.verbose) {
|
|
309
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
process.exit(0);
|
|
313
|
+
} else {
|
|
314
|
+
spinner.fail(`Failed (${result.status})`);
|
|
315
|
+
console.error(chalk.red(result.message));
|
|
316
|
+
if (options.verbose && result.data) {
|
|
317
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
318
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
319
|
+
}
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
} catch (error) {
|
|
323
|
+
spinner.fail('Error occurred');
|
|
324
|
+
console.error(chalk.red('Error:'), error.message);
|
|
325
|
+
if (options.verbose) {
|
|
326
|
+
console.error(error.stack);
|
|
327
|
+
}
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
export default key;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { createClient, handleError } from '../lib/api.js';
|
|
5
|
+
|
|
6
|
+
const login = new Command('login');
|
|
7
|
+
|
|
8
|
+
login
|
|
9
|
+
.description('Manage login');
|
|
10
|
+
|
|
11
|
+
login
|
|
12
|
+
.command('create')
|
|
13
|
+
.description('push sign-in request
|
|
14
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
15
|
+
')
|
|
16
|
+
.requiredOption('--callback <value>', 'URI App will connect to')
|
|
17
|
+
.option('--json', 'Output as JSON')
|
|
18
|
+
.option('-v, --verbose', 'Verbose output')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const spinner = ora('push sign-in request
|
|
21
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
22
|
+
...').start();
|
|
23
|
+
try {
|
|
24
|
+
const client = createClient();
|
|
25
|
+
const queryParams = {};
|
|
26
|
+
if (options.callback) queryParams.callback = options.callback;
|
|
27
|
+
|
|
28
|
+
const response = await client.post(`/login`);
|
|
29
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
30
|
+
|
|
31
|
+
spinner.succeed('Success');
|
|
32
|
+
|
|
33
|
+
if (result.success) {
|
|
34
|
+
if (options.json) {
|
|
35
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
36
|
+
} else {
|
|
37
|
+
// Create output
|
|
38
|
+
const item = result.data;
|
|
39
|
+
console.log(chalk.green('✓ Created successfully!'));
|
|
40
|
+
if (item.id) console.log('ID:', chalk.cyan(item.id));
|
|
41
|
+
}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
} else {
|
|
44
|
+
spinner.fail(`Failed (${result.status})`);
|
|
45
|
+
console.error(chalk.red(result.message));
|
|
46
|
+
if (options.verbose && result.data) {
|
|
47
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
48
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
spinner.fail('Error occurred');
|
|
54
|
+
console.error(chalk.red('Error:'), error.message);
|
|
55
|
+
if (options.verbose) {
|
|
56
|
+
console.error(error.stack);
|
|
57
|
+
}
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default login;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { createClient, handleError } from '../lib/api.js';
|
|
5
|
+
|
|
6
|
+
const scope = new Command('scope');
|
|
7
|
+
|
|
8
|
+
scope
|
|
9
|
+
.description('Manage scope');
|
|
10
|
+
|
|
11
|
+
scope
|
|
12
|
+
.command('create')
|
|
13
|
+
.description('scope verification request
|
|
14
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
15
|
+
')
|
|
16
|
+
.option('--test <value>', 'test only mode, using test issuer')
|
|
17
|
+
.option('--json', 'Output as JSON')
|
|
18
|
+
.option('-v, --verbose', 'Verbose output')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const spinner = ora('scope verification request
|
|
21
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
22
|
+
...').start();
|
|
23
|
+
try {
|
|
24
|
+
const client = createClient();
|
|
25
|
+
const queryParams = {};
|
|
26
|
+
if (options.test) queryParams.test = options.test;
|
|
27
|
+
|
|
28
|
+
const response = await client.post(`/scope`);
|
|
29
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
30
|
+
|
|
31
|
+
spinner.succeed('Success');
|
|
32
|
+
|
|
33
|
+
if (result.success) {
|
|
34
|
+
if (options.json) {
|
|
35
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
36
|
+
} else {
|
|
37
|
+
// Create output
|
|
38
|
+
const item = result.data;
|
|
39
|
+
console.log(chalk.green('✓ Created successfully!'));
|
|
40
|
+
if (item.id) console.log('ID:', chalk.cyan(item.id));
|
|
41
|
+
}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
} else {
|
|
44
|
+
spinner.fail(`Failed (${result.status})`);
|
|
45
|
+
console.error(chalk.red(result.message));
|
|
46
|
+
if (options.verbose && result.data) {
|
|
47
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
48
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
spinner.fail('Error occurred');
|
|
54
|
+
console.error(chalk.red('Error:'), error.message);
|
|
55
|
+
if (options.verbose) {
|
|
56
|
+
console.error(error.stack);
|
|
57
|
+
}
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
scope
|
|
63
|
+
.command('delete <job>')
|
|
64
|
+
.description('delete a verification job')
|
|
65
|
+
.option('--json', 'Output as JSON')
|
|
66
|
+
.option('-v, --verbose', 'Verbose output')
|
|
67
|
+
.action(async (job, options) => {
|
|
68
|
+
const spinner = ora('delete a verification job...').start();
|
|
69
|
+
try {
|
|
70
|
+
const client = createClient();
|
|
71
|
+
const response = await client.delete(`/scope/${job}`);
|
|
72
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
73
|
+
|
|
74
|
+
spinner.succeed('Success');
|
|
75
|
+
|
|
76
|
+
if (result.success) {
|
|
77
|
+
if (options.json) {
|
|
78
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(chalk.green('✓ Deleted successfully'));
|
|
81
|
+
}
|
|
82
|
+
process.exit(0);
|
|
83
|
+
} else {
|
|
84
|
+
spinner.fail(`Failed (${result.status})`);
|
|
85
|
+
console.error(chalk.red(result.message));
|
|
86
|
+
if (options.verbose && result.data) {
|
|
87
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
88
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
89
|
+
}
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
spinner.fail('Error occurred');
|
|
94
|
+
console.error(chalk.red('Error:'), error.message);
|
|
95
|
+
if (options.verbose) {
|
|
96
|
+
console.error(error.stack);
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
scope
|
|
103
|
+
.command('get <job>')
|
|
104
|
+
.description('get the status / current content of a verification job')
|
|
105
|
+
.option('--json', 'Output as JSON')
|
|
106
|
+
.option('-v, --verbose', 'Verbose output')
|
|
107
|
+
.action(async (job, options) => {
|
|
108
|
+
const spinner = ora('get the status / current content of a verification job...').start();
|
|
109
|
+
try {
|
|
110
|
+
const client = createClient();
|
|
111
|
+
const response = await client.get(`/scope/${job}`);
|
|
112
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
113
|
+
|
|
114
|
+
spinner.succeed('Success');
|
|
115
|
+
|
|
116
|
+
if (result.success) {
|
|
117
|
+
if (options.json) {
|
|
118
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
119
|
+
} else {
|
|
120
|
+
// Detail output
|
|
121
|
+
const item = result.data;
|
|
122
|
+
console.log(chalk.bold('Details:'));
|
|
123
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
124
|
+
Object.entries(item).slice(0, 10).forEach(([key, value]) => {
|
|
125
|
+
console.log(chalk.cyan(key + ':'), String(value).slice(0, 100));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
process.exit(0);
|
|
129
|
+
} else {
|
|
130
|
+
spinner.fail(`Failed (${result.status})`);
|
|
131
|
+
console.error(chalk.red(result.message));
|
|
132
|
+
if (options.verbose && result.data) {
|
|
133
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
134
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
135
|
+
}
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
spinner.fail('Error occurred');
|
|
140
|
+
console.error(chalk.red('Error:'), error.message);
|
|
141
|
+
if (options.verbose) {
|
|
142
|
+
console.error(error.stack);
|
|
143
|
+
}
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
scope
|
|
149
|
+
.command('create <job>')
|
|
150
|
+
.description('this is a scope confirmation')
|
|
151
|
+
.option('--json', 'Output as JSON')
|
|
152
|
+
.option('-v, --verbose', 'Verbose output')
|
|
153
|
+
.action(async (job, options) => {
|
|
154
|
+
const spinner = ora('this is a scope confirmation...').start();
|
|
155
|
+
try {
|
|
156
|
+
const client = createClient();
|
|
157
|
+
const response = await client.post(`/scope/${job}`);
|
|
158
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
159
|
+
|
|
160
|
+
spinner.succeed('Success');
|
|
161
|
+
|
|
162
|
+
if (result.success) {
|
|
163
|
+
if (options.json) {
|
|
164
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
165
|
+
} else {
|
|
166
|
+
// Create output
|
|
167
|
+
const item = result.data;
|
|
168
|
+
console.log(chalk.green('✓ Created successfully!'));
|
|
169
|
+
if (item.id) console.log('ID:', chalk.cyan(item.id));
|
|
170
|
+
}
|
|
171
|
+
process.exit(0);
|
|
172
|
+
} else {
|
|
173
|
+
spinner.fail(`Failed (${result.status})`);
|
|
174
|
+
console.error(chalk.red(result.message));
|
|
175
|
+
if (options.verbose && result.data) {
|
|
176
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
177
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
178
|
+
}
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
spinner.fail('Error occurred');
|
|
183
|
+
console.error(chalk.red('Error:'), error.message);
|
|
184
|
+
if (options.verbose) {
|
|
185
|
+
console.error(error.stack);
|
|
186
|
+
}
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
scope
|
|
192
|
+
.command('update <job>')
|
|
193
|
+
.description('authority updates a JWT with its signature
|
|
194
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
195
|
+
')
|
|
196
|
+
.option('--json', 'Output as JSON')
|
|
197
|
+
.option('-v, --verbose', 'Verbose output')
|
|
198
|
+
.action(async (job, options) => {
|
|
199
|
+
const spinner = ora('authority updates a JWT with its signature
|
|
200
|
+
See: https://github.com/skion/authentiq/wiki/JWT-Examples
|
|
201
|
+
...').start();
|
|
202
|
+
try {
|
|
203
|
+
const client = createClient();
|
|
204
|
+
const response = await client.put(`/scope/${job}`);
|
|
205
|
+
const result = { success: true, status: response.status, data: response.data };
|
|
206
|
+
|
|
207
|
+
spinner.succeed('Success');
|
|
208
|
+
|
|
209
|
+
if (result.success) {
|
|
210
|
+
if (options.json) {
|
|
211
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
212
|
+
} else {
|
|
213
|
+
// Update/other output
|
|
214
|
+
console.log(chalk.green('✓ Operation completed successfully'));
|
|
215
|
+
if (options.verbose) {
|
|
216
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
process.exit(0);
|
|
220
|
+
} else {
|
|
221
|
+
spinner.fail(`Failed (${result.status})`);
|
|
222
|
+
console.error(chalk.red(result.message));
|
|
223
|
+
if (options.verbose && result.data) {
|
|
224
|
+
console.error(chalk.yellow('\nResponse data:'));
|
|
225
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
226
|
+
}
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
spinner.fail('Error occurred');
|
|
231
|
+
console.error(chalk.red('Error:'), error.message);
|
|
232
|
+
if (options.verbose) {
|
|
233
|
+
console.error(error.stack);
|
|
234
|
+
}
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export default scope;
|
package/src/lib/api.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { loadConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create axios instance with configuration
|
|
6
|
+
*/
|
|
7
|
+
function createClient(options = {}) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const baseURL = options.baseUrl || config.baseUrl || 'https://6-dot-authentiqio.appspot.com';
|
|
10
|
+
|
|
11
|
+
const instance = axios.create({
|
|
12
|
+
baseURL,
|
|
13
|
+
timeout: options.timeout || config.timeout || 30000,
|
|
14
|
+
headers: {
|
|
15
|
+
'User-Agent': '@ktmcp-cli/authentiq-api/1.0.0',
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Add authentication
|
|
21
|
+
instance.interceptors.request.use((config) => {
|
|
22
|
+
// No authentication configured
|
|
23
|
+
return config;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return instance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handle API errors consistently
|
|
31
|
+
*/
|
|
32
|
+
function handleError(error) {
|
|
33
|
+
if (error.response) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
status: error.response.status,
|
|
37
|
+
statusText: error.response.statusText,
|
|
38
|
+
message: error.response.data?.message ||
|
|
39
|
+
error.response.data?.error ||
|
|
40
|
+
'API request failed',
|
|
41
|
+
data: error.response.data
|
|
42
|
+
};
|
|
43
|
+
} else if (error.request) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
status: 0,
|
|
47
|
+
message: 'No response from server',
|
|
48
|
+
error: error.message
|
|
49
|
+
};
|
|
50
|
+
} else {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
status: 0,
|
|
54
|
+
message: error.message
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { createClient, handleError };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.authentiq-api');
|
|
9
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
baseUrl: process.env.API_BASE_URL || 'https://6-dot-authentiqio.appspot.com',
|
|
13
|
+
apiKey: process.env.API_KEY || '',
|
|
14
|
+
timeout: parseInt(process.env.API_TIMEOUT || '30000', 10)
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function loadConfig() {
|
|
18
|
+
if (existsSync(CONFIG_FILE)) {
|
|
19
|
+
try {
|
|
20
|
+
const fileConfig = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
21
|
+
return { ...DEFAULT_CONFIG, ...fileConfig };
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error reading config file:', error.message);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return DEFAULT_CONFIG;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function saveConfig(config) {
|
|
30
|
+
try {
|
|
31
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
32
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
35
|
+
return true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error saving config:', error.message);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getConfig(key) {
|
|
43
|
+
const config = loadConfig();
|
|
44
|
+
return config[key];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setConfig(key, value) {
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
config[key] = value;
|
|
50
|
+
return saveConfig(config);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
loadConfig,
|
|
55
|
+
saveConfig,
|
|
56
|
+
getConfig,
|
|
57
|
+
setConfig
|
|
58
|
+
};
|