javascript-solid-server 0.0.10 → 0.0.12

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.
@@ -15,7 +15,17 @@
15
15
  "Bash(npm test:*)",
16
16
  "Bash(git add:*)",
17
17
  "WebFetch(domain:solid.github.io)",
18
- "Bash(node:*)"
18
+ "Bash(node:*)",
19
+ "WebFetch(domain:solidservers.org)",
20
+ "WebFetch(domain:solid-contrib.github.io)",
21
+ "Bash(git clone:*)",
22
+ "Bash(chmod:*)",
23
+ "Bash(JSS_PORT=4000 JSS_CONNEG=true node bin/jss.js:*)",
24
+ "Bash(find:*)",
25
+ "Bash(timeout 5 node:*)",
26
+ "Bash(npm view:*)",
27
+ "Bash(npm ls:*)",
28
+ "Bash(timeout 10 node:*)"
19
29
  ]
20
30
  }
21
31
  }
package/README.md CHANGED
@@ -54,17 +54,20 @@ npm run benchmark
54
54
 
55
55
  ## Features
56
56
 
57
- ### Implemented (v0.0.10)
57
+ ### Implemented (v0.0.12)
58
58
 
59
59
  - **LDP CRUD Operations** - GET, PUT, POST, DELETE, HEAD
60
60
  - **N3 Patch** - Solid's native patch format for RDF updates
61
61
  - **SPARQL Update** - Standard SPARQL UPDATE protocol for PATCH
62
62
  - **Conditional Requests** - If-Match/If-None-Match headers (304, 412)
63
+ - **CLI & Config** - `jss` command with config file/env var support
64
+ - **SSL/TLS** - HTTPS support with certificate configuration
63
65
  - **WebSocket Notifications** - Real-time updates via solid-0.1 protocol (SolidOS compatible)
64
66
  - **Container Management** - Create, list, and manage containers
65
67
  - **Multi-user Pods** - Create pods at `/<username>/`
66
68
  - **WebID Profiles** - JSON-LD structured data in HTML at pod root
67
69
  - **Web Access Control (WAC)** - `.acl` file-based authorization
70
+ - **Solid-OIDC Identity Provider** - Built-in IdP with DPoP, dynamic registration
68
71
  - **Solid-OIDC Resource Server** - Accept DPoP-bound access tokens from external IdPs
69
72
  - **Simple Auth Tokens** - Built-in token authentication for development
70
73
  - **Content Negotiation** - Optional Turtle <-> JSON-LD conversion
@@ -92,18 +95,77 @@ npm run benchmark
92
95
 
93
96
  ```bash
94
97
  npm install
98
+
99
+ # Or install globally
100
+ npm install -g javascript-solid-server
95
101
  ```
96
102
 
97
- ### Running
103
+ ### Quick Start
98
104
 
99
105
  ```bash
100
- # Start server (default port 3000)
101
- npm start
106
+ # Initialize configuration (interactive)
107
+ jss init
108
+
109
+ # Start server
110
+ jss start
111
+
112
+ # Or with options
113
+ jss start --port 8443 --ssl-key ./key.pem --ssl-cert ./cert.pem
114
+ ```
115
+
116
+ ### CLI Commands
102
117
 
103
- # Development mode with watch
104
- npm dev
118
+ ```bash
119
+ jss start [options] # Start the server
120
+ jss init [options] # Initialize configuration
121
+ jss --help # Show help
105
122
  ```
106
123
 
124
+ ### Start Options
125
+
126
+ | Option | Description | Default |
127
+ |--------|-------------|---------|
128
+ | `-p, --port <n>` | Port to listen on | 3000 |
129
+ | `-h, --host <addr>` | Host to bind to | 0.0.0.0 |
130
+ | `-r, --root <path>` | Data directory | ./data |
131
+ | `-c, --config <file>` | Config file path | - |
132
+ | `--ssl-key <path>` | SSL private key (PEM) | - |
133
+ | `--ssl-cert <path>` | SSL certificate (PEM) | - |
134
+ | `--conneg` | Enable Turtle support | false |
135
+ | `--notifications` | Enable WebSocket | false |
136
+ | `--idp` | Enable built-in IdP | false |
137
+ | `--idp-issuer <url>` | IdP issuer URL | (auto) |
138
+ | `-q, --quiet` | Suppress logs | false |
139
+
140
+ ### Environment Variables
141
+
142
+ All options can be set via environment variables with `JSS_` prefix:
143
+
144
+ ```bash
145
+ export JSS_PORT=8443
146
+ export JSS_SSL_KEY=/path/to/key.pem
147
+ export JSS_SSL_CERT=/path/to/cert.pem
148
+ export JSS_CONNEG=true
149
+ jss start
150
+ ```
151
+
152
+ ### Config File
153
+
154
+ Create `config.json`:
155
+
156
+ ```json
157
+ {
158
+ "port": 8443,
159
+ "root": "./data",
160
+ "sslKey": "./ssl/key.pem",
161
+ "sslCert": "./ssl/cert.pem",
162
+ "conneg": true,
163
+ "notifications": true
164
+ }
165
+ ```
166
+
167
+ Then: `jss start --config config.json`
168
+
107
169
  ### Creating a Pod
108
170
 
109
171
  ```bash
@@ -215,9 +277,38 @@ Use the token returned from pod creation:
215
277
  curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/alice/private/
216
278
  ```
217
279
 
218
- ### Solid-OIDC (Production)
280
+ ### Built-in Identity Provider (v0.0.12+)
281
+
282
+ Enable the built-in Solid-OIDC Identity Provider:
283
+
284
+ ```bash
285
+ jss start --idp
286
+ ```
287
+
288
+ With IdP enabled, pod creation requires email and password:
289
+
290
+ ```bash
291
+ curl -X POST http://localhost:3000/.pods \
292
+ -H "Content-Type: application/json" \
293
+ -d '{"name": "alice", "email": "alice@example.com", "password": "secret123"}'
294
+ ```
295
+
296
+ Response:
297
+ ```json
298
+ {
299
+ "name": "alice",
300
+ "webId": "http://localhost:3000/alice/#me",
301
+ "podUri": "http://localhost:3000/alice/",
302
+ "idpIssuer": "http://localhost:3000",
303
+ "loginUrl": "http://localhost:3000/idp/auth"
304
+ }
305
+ ```
306
+
307
+ OIDC Discovery: `/.well-known/openid-configuration`
308
+
309
+ ### Solid-OIDC (External IdP)
219
310
 
220
- The server accepts DPoP-bound access tokens from external Solid identity providers:
311
+ The server also accepts DPoP-bound access tokens from external Solid identity providers:
221
312
 
222
313
  ```bash
223
314
  curl -H "Authorization: DPoP ACCESS_TOKEN" \
@@ -264,7 +355,7 @@ Server: pub http://localhost:3000/alice/public/data.json (on change)
264
355
  npm test
265
356
  ```
266
357
 
267
- Currently passing: **136 tests**
358
+ Currently passing: **174 tests** (including 27 conformance tests)
268
359
 
269
360
  ## Project Structure
270
361
 
@@ -296,6 +387,14 @@ src/
296
387
  │ ├── index.js # WebSocket plugin
297
388
  │ ├── events.js # Event emitter
298
389
  │ └── websocket.js # solid-0.1 protocol
390
+ ├── idp/
391
+ │ ├── index.js # Identity Provider plugin
392
+ │ ├── provider.js # oidc-provider config
393
+ │ ├── adapter.js # Filesystem adapter
394
+ │ ├── accounts.js # User account management
395
+ │ ├── keys.js # JWKS key management
396
+ │ ├── interactions.js # Login/consent handlers
397
+ │ └── views.js # HTML templates
299
398
  ├── rdf/
300
399
  │ ├── turtle.js # Turtle <-> JSON-LD
301
400
  │ └── conneg.js # Content negotiation
@@ -313,6 +412,8 @@ Minimal dependencies for a fast, secure server:
313
412
  - **fs-extra** - Enhanced file operations
314
413
  - **jose** - JWT/JWK handling for Solid-OIDC
315
414
  - **n3** - Turtle parsing (only used when conneg enabled)
415
+ - **oidc-provider** - OpenID Connect Identity Provider (only when IdP enabled)
416
+ - **bcrypt** - Password hashing (only when IdP enabled)
316
417
 
317
418
  ## License
318
419
 
package/bin/jss.js ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * JavaScript Solid Server CLI
5
+ *
6
+ * Usage:
7
+ * jss start [options] Start the server
8
+ * jss init Initialize configuration
9
+ */
10
+
11
+ import { Command } from 'commander';
12
+ import { createServer } from '../src/server.js';
13
+ import { loadConfig, saveConfig, printConfig, defaults } from '../src/config.js';
14
+ import fs from 'fs-extra';
15
+ import path from 'path';
16
+ import { fileURLToPath } from 'url';
17
+ import readline from 'readline';
18
+
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const pkg = JSON.parse(await fs.readFile(path.join(__dirname, '../package.json'), 'utf8'));
21
+
22
+ const program = new Command();
23
+
24
+ program
25
+ .name('jss')
26
+ .description('JavaScript Solid Server - A minimal, fast, JSON-LD native Solid server')
27
+ .version(pkg.version);
28
+
29
+ /**
30
+ * Start command
31
+ */
32
+ program
33
+ .command('start')
34
+ .description('Start the Solid server')
35
+ .option('-p, --port <number>', 'Port to listen on', parseInt)
36
+ .option('-h, --host <address>', 'Host to bind to')
37
+ .option('-r, --root <path>', 'Data directory')
38
+ .option('-c, --config <file>', 'Config file path')
39
+ .option('--ssl-key <path>', 'Path to SSL private key (PEM)')
40
+ .option('--ssl-cert <path>', 'Path to SSL certificate (PEM)')
41
+ .option('--multiuser', 'Enable multi-user mode')
42
+ .option('--no-multiuser', 'Disable multi-user mode')
43
+ .option('--conneg', 'Enable content negotiation (Turtle support)')
44
+ .option('--no-conneg', 'Disable content negotiation')
45
+ .option('--notifications', 'Enable WebSocket notifications')
46
+ .option('--no-notifications', 'Disable WebSocket notifications')
47
+ .option('--idp', 'Enable built-in Identity Provider')
48
+ .option('--no-idp', 'Disable built-in Identity Provider')
49
+ .option('--idp-issuer <url>', 'IdP issuer URL (defaults to server URL)')
50
+ .option('-q, --quiet', 'Suppress log output')
51
+ .option('--print-config', 'Print configuration and exit')
52
+ .action(async (options) => {
53
+ try {
54
+ const config = await loadConfig(options, options.config);
55
+
56
+ if (options.printConfig) {
57
+ printConfig(config);
58
+ process.exit(0);
59
+ }
60
+
61
+ // Determine IdP issuer URL
62
+ const protocol = config.ssl ? 'https' : 'http';
63
+ const serverHost = config.host === '0.0.0.0' ? 'localhost' : config.host;
64
+ const baseUrl = `${protocol}://${serverHost}:${config.port}`;
65
+ const idpIssuer = config.idpIssuer || baseUrl;
66
+
67
+ // Create and start server
68
+ const server = createServer({
69
+ logger: config.logger,
70
+ conneg: config.conneg,
71
+ notifications: config.notifications,
72
+ idp: config.idp,
73
+ idpIssuer: idpIssuer,
74
+ ssl: config.ssl ? {
75
+ key: await fs.readFile(config.sslKey),
76
+ cert: await fs.readFile(config.sslCert),
77
+ } : null,
78
+ root: config.root,
79
+ });
80
+
81
+ await server.listen({ port: config.port, host: config.host });
82
+
83
+ if (!config.quiet) {
84
+ console.log(`\n JavaScript Solid Server v${pkg.version}`);
85
+ console.log(` ${baseUrl}/`);
86
+ console.log(`\n Data: ${path.resolve(config.root)}`);
87
+ if (config.ssl) console.log(' SSL: enabled');
88
+ if (config.conneg) console.log(' Conneg: enabled');
89
+ if (config.notifications) console.log(' WebSocket: enabled');
90
+ if (config.idp) console.log(` IdP: ${idpIssuer}`);
91
+ console.log('\n Press Ctrl+C to stop\n');
92
+ }
93
+
94
+ // Handle shutdown
95
+ const shutdown = async () => {
96
+ if (!config.quiet) console.log('\n Shutting down...');
97
+ await server.close();
98
+ process.exit(0);
99
+ };
100
+
101
+ process.on('SIGINT', shutdown);
102
+ process.on('SIGTERM', shutdown);
103
+
104
+ } catch (err) {
105
+ console.error(`Error: ${err.message}`);
106
+ process.exit(1);
107
+ }
108
+ });
109
+
110
+ /**
111
+ * Init command - interactive configuration
112
+ */
113
+ program
114
+ .command('init')
115
+ .description('Initialize server configuration')
116
+ .option('-c, --config <file>', 'Config file path', './config.json')
117
+ .option('-y, --yes', 'Accept defaults without prompting')
118
+ .action(async (options) => {
119
+ const configFile = path.resolve(options.config);
120
+
121
+ // Check if config already exists
122
+ if (await fs.pathExists(configFile)) {
123
+ console.log(`Config file already exists: ${configFile}`);
124
+ const overwrite = options.yes ? true : await confirm('Overwrite?');
125
+ if (!overwrite) {
126
+ console.log('Aborted.');
127
+ process.exit(0);
128
+ }
129
+ }
130
+
131
+ let config;
132
+
133
+ if (options.yes) {
134
+ // Use defaults
135
+ config = { ...defaults };
136
+ } else {
137
+ // Interactive prompts
138
+ console.log('\n JavaScript Solid Server Setup\n');
139
+
140
+ config = {
141
+ port: await prompt('Port', defaults.port),
142
+ root: await prompt('Data directory', defaults.root),
143
+ conneg: await confirm('Enable content negotiation (Turtle support)?', defaults.conneg),
144
+ notifications: await confirm('Enable WebSocket notifications?', defaults.notifications),
145
+ };
146
+
147
+ // Ask about SSL
148
+ const useSSL = await confirm('Configure SSL?', false);
149
+ if (useSSL) {
150
+ config.sslKey = await prompt('SSL key path', './ssl/key.pem');
151
+ config.sslCert = await prompt('SSL certificate path', './ssl/cert.pem');
152
+ }
153
+
154
+ // Ask about IdP
155
+ config.idp = await confirm('Enable built-in Identity Provider?', false);
156
+ if (config.idp) {
157
+ const customIssuer = await confirm('Use custom issuer URL?', false);
158
+ if (customIssuer) {
159
+ config.idpIssuer = await prompt('IdP issuer URL', 'https://example.com');
160
+ }
161
+ }
162
+
163
+ console.log('');
164
+ }
165
+
166
+ // Save config
167
+ await saveConfig(config, configFile);
168
+ console.log(`Configuration saved to: ${configFile}`);
169
+
170
+ // Create data directory
171
+ const dataDir = path.resolve(config.root);
172
+ await fs.ensureDir(dataDir);
173
+ console.log(`Data directory created: ${dataDir}`);
174
+
175
+ console.log('\nRun `jss start` to start the server.\n');
176
+ });
177
+
178
+ /**
179
+ * Helper: Prompt for input
180
+ */
181
+ async function prompt(question, defaultValue) {
182
+ const rl = readline.createInterface({
183
+ input: process.stdin,
184
+ output: process.stdout
185
+ });
186
+
187
+ return new Promise((resolve) => {
188
+ const defaultStr = defaultValue !== undefined ? ` (${defaultValue})` : '';
189
+ rl.question(` ${question}${defaultStr}: `, (answer) => {
190
+ rl.close();
191
+ const value = answer.trim() || defaultValue;
192
+ // Parse numbers
193
+ if (typeof defaultValue === 'number' && !isNaN(value)) {
194
+ resolve(parseInt(value, 10));
195
+ } else {
196
+ resolve(value);
197
+ }
198
+ });
199
+ });
200
+ }
201
+
202
+ /**
203
+ * Helper: Confirm yes/no
204
+ */
205
+ async function confirm(question, defaultValue = false) {
206
+ const rl = readline.createInterface({
207
+ input: process.stdin,
208
+ output: process.stdout
209
+ });
210
+
211
+ return new Promise((resolve) => {
212
+ const hint = defaultValue ? '[Y/n]' : '[y/N]';
213
+ rl.question(` ${question} ${hint}: `, (answer) => {
214
+ rl.close();
215
+ const normalized = answer.trim().toLowerCase();
216
+ if (normalized === '') {
217
+ resolve(defaultValue);
218
+ } else {
219
+ resolve(normalized === 'y' || normalized === 'yes');
220
+ }
221
+ });
222
+ });
223
+ }
224
+
225
+ // Parse and run
226
+ program.parse();
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
7
+ "bin": {
8
+ "jss": "./bin/jss.js"
9
+ },
7
10
  "repository": {
8
11
  "type": "git",
9
12
  "url": "git+https://github.com/JavaScriptSolidServer/JavaScriptSolidServer.git"
@@ -13,17 +16,21 @@
13
16
  },
14
17
  "homepage": "https://github.com/JavaScriptSolidServer/JavaScriptSolidServer#readme",
15
18
  "scripts": {
16
- "start": "node src/index.js",
17
- "dev": "node --watch src/index.js",
19
+ "start": "node bin/jss.js start",
20
+ "dev": "node --watch bin/jss.js start",
18
21
  "test": "node --test --test-concurrency=1",
19
22
  "benchmark": "node benchmark.js"
20
23
  },
21
24
  "dependencies": {
25
+ "@fastify/middie": "^8.3.3",
22
26
  "@fastify/websocket": "^8.3.1",
27
+ "bcrypt": "^6.0.0",
28
+ "commander": "^14.0.2",
23
29
  "fastify": "^4.25.2",
24
30
  "fs-extra": "^11.2.0",
25
31
  "jose": "^6.1.3",
26
- "n3": "^1.26.0"
32
+ "n3": "^1.26.0",
33
+ "oidc-provider": "^9.6.0"
27
34
  },
28
35
  "engines": {
29
36
  "node": ">=18.0.0"
package/src/config.js ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Configuration Loading
3
+ *
4
+ * Loads config from (in order of precedence):
5
+ * 1. CLI arguments (highest)
6
+ * 2. Environment variables (JSS_*)
7
+ * 3. Config file (config.json)
8
+ * 4. Defaults (lowest)
9
+ */
10
+
11
+ import fs from 'fs-extra';
12
+ import path from 'path';
13
+
14
+ /**
15
+ * Default configuration values
16
+ */
17
+ export const defaults = {
18
+ // Server
19
+ port: 3000,
20
+ host: '0.0.0.0',
21
+ root: './data',
22
+
23
+ // SSL
24
+ sslKey: null,
25
+ sslCert: null,
26
+
27
+ // Features
28
+ multiuser: true,
29
+ conneg: false,
30
+ notifications: false,
31
+
32
+ // Identity Provider
33
+ idp: false,
34
+ idpIssuer: null,
35
+
36
+ // Logging
37
+ logger: true,
38
+ quiet: false,
39
+
40
+ // Paths
41
+ configPath: './.jss',
42
+ };
43
+
44
+ /**
45
+ * Map of environment variable names to config keys
46
+ */
47
+ const envMap = {
48
+ JSS_PORT: 'port',
49
+ JSS_HOST: 'host',
50
+ JSS_ROOT: 'root',
51
+ JSS_SSL_KEY: 'sslKey',
52
+ JSS_SSL_CERT: 'sslCert',
53
+ JSS_MULTIUSER: 'multiuser',
54
+ JSS_CONNEG: 'conneg',
55
+ JSS_NOTIFICATIONS: 'notifications',
56
+ JSS_QUIET: 'quiet',
57
+ JSS_CONFIG_PATH: 'configPath',
58
+ JSS_IDP: 'idp',
59
+ JSS_IDP_ISSUER: 'idpIssuer',
60
+ };
61
+
62
+ /**
63
+ * Parse a value from environment variable string
64
+ */
65
+ function parseEnvValue(value, key) {
66
+ if (value === undefined) return undefined;
67
+
68
+ // Boolean values
69
+ if (value.toLowerCase() === 'true') return true;
70
+ if (value.toLowerCase() === 'false') return false;
71
+
72
+ // Numeric values for known numeric keys
73
+ if (key === 'port' && !isNaN(value)) {
74
+ return parseInt(value, 10);
75
+ }
76
+
77
+ return value;
78
+ }
79
+
80
+ /**
81
+ * Load configuration from environment variables
82
+ */
83
+ function loadEnvConfig() {
84
+ const config = {};
85
+
86
+ for (const [envVar, configKey] of Object.entries(envMap)) {
87
+ const value = process.env[envVar];
88
+ if (value !== undefined) {
89
+ config[configKey] = parseEnvValue(value, configKey);
90
+ }
91
+ }
92
+
93
+ return config;
94
+ }
95
+
96
+ /**
97
+ * Load configuration from a JSON file
98
+ */
99
+ async function loadFileConfig(configFile) {
100
+ if (!configFile) return {};
101
+
102
+ try {
103
+ const fullPath = path.resolve(configFile);
104
+ if (await fs.pathExists(fullPath)) {
105
+ const content = await fs.readFile(fullPath, 'utf8');
106
+ return JSON.parse(content);
107
+ }
108
+ } catch (e) {
109
+ console.error(`Warning: Failed to load config file: ${e.message}`);
110
+ }
111
+
112
+ return {};
113
+ }
114
+
115
+ /**
116
+ * Merge configuration sources
117
+ * @param {object} cliOptions - Options from command line
118
+ * @param {string} configFile - Path to config file (optional)
119
+ * @returns {Promise<object>} Merged configuration
120
+ */
121
+ export async function loadConfig(cliOptions = {}, configFile = null) {
122
+ // Load from file first
123
+ const fileConfig = await loadFileConfig(configFile || cliOptions.config);
124
+
125
+ // Load from environment
126
+ const envConfig = loadEnvConfig();
127
+
128
+ // Merge in order: defaults < file < env < cli
129
+ const config = {
130
+ ...defaults,
131
+ ...fileConfig,
132
+ ...envConfig,
133
+ ...filterUndefined(cliOptions),
134
+ };
135
+
136
+ // Derive additional settings
137
+ if (config.quiet) {
138
+ config.logger = false;
139
+ }
140
+
141
+ // Validate SSL config
142
+ if ((config.sslKey && !config.sslCert) || (!config.sslKey && config.sslCert)) {
143
+ throw new Error('Both --ssl-key and --ssl-cert must be provided together');
144
+ }
145
+
146
+ config.ssl = !!(config.sslKey && config.sslCert);
147
+
148
+ return config;
149
+ }
150
+
151
+ /**
152
+ * Filter out undefined values from an object
153
+ */
154
+ function filterUndefined(obj) {
155
+ const result = {};
156
+ for (const [key, value] of Object.entries(obj)) {
157
+ if (value !== undefined) {
158
+ result[key] = value;
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+
164
+ /**
165
+ * Save configuration to a file
166
+ */
167
+ export async function saveConfig(config, configFile) {
168
+ const toSave = { ...config };
169
+ // Remove derived/runtime values
170
+ delete toSave.ssl;
171
+ delete toSave.logger;
172
+
173
+ await fs.ensureDir(path.dirname(configFile));
174
+ await fs.writeFile(configFile, JSON.stringify(toSave, null, 2));
175
+ }
176
+
177
+ /**
178
+ * Print configuration (for debugging)
179
+ */
180
+ export function printConfig(config) {
181
+ console.log('\nConfiguration:');
182
+ console.log('─'.repeat(40));
183
+ console.log(` Port: ${config.port}`);
184
+ console.log(` Host: ${config.host}`);
185
+ console.log(` Root: ${path.resolve(config.root)}`);
186
+ console.log(` SSL: ${config.ssl ? 'enabled' : 'disabled'}`);
187
+ console.log(` Multi-user: ${config.multiuser}`);
188
+ console.log(` Conneg: ${config.conneg}`);
189
+ console.log(` Notifications: ${config.notifications}`);
190
+ console.log(` IdP: ${config.idp ? (config.idpIssuer || 'enabled') : 'disabled'}`);
191
+ console.log('─'.repeat(40));
192
+ }