@incorta/auth 1.0.0 → 1.1.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/README.md CHANGED
@@ -8,7 +8,7 @@ guide; this is the API surface.
8
8
 
9
9
  | Import | What's in it |
10
10
  | ----------------------- | ------------------------------------------------------------------- |
11
- | `@incorta/auth` | `createIncortaAuth`, `registerClient`, types (`IncortaUser`, `AuthSession`, …) |
11
+ | `@incorta/auth` | `createIncortaAuth`, `registerClient`, `getClient`, `updateClientRedirectUris`, types (`IncortaUser`, `AuthSession`, …) |
12
12
  | `@incorta/auth/react` | `IncortaAuthProvider`, `useAuth`, `useUser`, `SignedIn`, `SignedOut`, `Protect`, `RedirectToLogin` |
13
13
  | `@incorta/auth/express` | `incortaAuth(auth)` middleware, `requireAuth(auth, options?)` |
14
14
  | `@incorta/auth/node` | `toNodeHandler(auth)`, `toWebRequest`, `sendWebResponse`, `getNodeSession` |
@@ -49,10 +49,17 @@ tokens. Nothing is stored server-side.
49
49
  ```bash
50
50
  incorta-auth register --url http://localhost:8080/incorta --tenant demo \
51
51
  --name "My App" --redirect https://myapp.example.com/auth/callback [--json]
52
+
53
+ incorta-auth show --url ... --tenant ... --client <id> --rat <token> [--json]
54
+
55
+ incorta-auth update-redirects --url ... --tenant ... --client <id> --rat <token> \
56
+ --redirect <url> [--redirect ...] [--replace] [--json]
52
57
  ```
53
58
 
54
- Registers the OAuth client via RFC 7591 dynamic registration and prints the
55
- `.env` entries.
59
+ `register` creates the OAuth client via RFC 7591 dynamic registration and
60
+ prints the `.env` entries (keep the registration access token). `show` and
61
+ `update-redirects` manage the registered client's allowed callback URLs —
62
+ adding by default, replacing the whole set with `--replace`.
56
63
 
57
64
  ## Testing
58
65
 
package/dist/cli.js CHANGED
@@ -4,6 +4,26 @@
4
4
  import { randomBytes } from "crypto";
5
5
 
6
6
  // src/core/register.ts
7
+ function registrationEndpoint(incortaUrl, tenant, clientId) {
8
+ const base = `${incortaUrl.replace(/\/+$/, "")}/oauth/${tenant}/register`;
9
+ return clientId ? `${base}/${clientId}` : base;
10
+ }
11
+ async function readJson(response, what) {
12
+ let body;
13
+ try {
14
+ body = await response.json();
15
+ } catch {
16
+ throw new Error(
17
+ `[incorta-auth] ${what} returned a non-JSON response (HTTP ${response.status}). Is the OAuth authorization server enabled on this cluster?`
18
+ );
19
+ }
20
+ if (!response.ok) {
21
+ throw new Error(
22
+ `[incorta-auth] ${what} failed (HTTP ${response.status}): ${body.error ?? "unknown_error"} \u2014 ${body.error_description ?? "no description"}`
23
+ );
24
+ }
25
+ return body;
26
+ }
7
27
  async function registerClient(options) {
8
28
  const {
9
29
  incortaUrl,
@@ -17,8 +37,7 @@ async function registerClient(options) {
17
37
  if (!redirectUris.length) {
18
38
  throw new Error("[incorta-auth] registerClient requires at least one redirect URI.");
19
39
  }
20
- const endpoint = `${incortaUrl.replace(/\/+$/, "")}/oauth/${tenant}/register`;
21
- const response = await fetchImpl(endpoint, {
40
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant), {
22
41
  method: "POST",
23
42
  headers: { "content-type": "application/json", accept: "application/json" },
24
43
  body: JSON.stringify({
@@ -29,17 +48,10 @@ async function registerClient(options) {
29
48
  token_endpoint_auth_method: "client_secret_basic"
30
49
  })
31
50
  });
32
- let body;
33
- try {
34
- body = await response.json();
35
- } catch {
51
+ const body = await readJson(response, "Client registration");
52
+ if (!body.client_id || !body.client_secret) {
36
53
  throw new Error(
37
- `[incorta-auth] Client registration returned a non-JSON response (HTTP ${response.status}). Is the OAuth authorization server enabled on this cluster?`
38
- );
39
- }
40
- if (!response.ok || !body.client_id || !body.client_secret) {
41
- throw new Error(
42
- `[incorta-auth] Client registration failed (HTTP ${response.status}): ${body.error ?? "unknown_error"} \u2014 ${body.error_description ?? "no description"}`
54
+ `[incorta-auth] Client registration failed: response is missing client_id/client_secret.`
43
55
  );
44
56
  }
45
57
  return {
@@ -49,6 +61,63 @@ async function registerClient(options) {
49
61
  registrationClientUri: body.registration_client_uri
50
62
  };
51
63
  }
64
+ function toClientInfo(body, fallbackClientId) {
65
+ return {
66
+ clientId: body.client_id ?? fallbackClientId,
67
+ name: body.client_name,
68
+ redirectUris: body.redirect_uris ?? [],
69
+ grantTypes: body.grant_types ?? [],
70
+ scope: body.scope
71
+ };
72
+ }
73
+ async function getClient(options) {
74
+ const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;
75
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
76
+ headers: {
77
+ accept: "application/json",
78
+ authorization: `Bearer ${registrationAccessToken}`
79
+ }
80
+ });
81
+ const body = await readJson(response, "Reading the client");
82
+ return toClientInfo(body, clientId);
83
+ }
84
+ async function updateClientRedirectUris(options) {
85
+ const {
86
+ incortaUrl,
87
+ tenant,
88
+ clientId,
89
+ registrationAccessToken,
90
+ redirectUris,
91
+ mode = "add",
92
+ fetchImpl = fetch
93
+ } = options;
94
+ if (!redirectUris.length) {
95
+ throw new Error("[incorta-auth] updateClientRedirectUris requires at least one redirect URI.");
96
+ }
97
+ const current = await getClient(options);
98
+ const nextUris = mode === "add" ? [.../* @__PURE__ */ new Set([...current.redirectUris, ...redirectUris])] : redirectUris;
99
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
100
+ method: "PUT",
101
+ headers: {
102
+ "content-type": "application/json",
103
+ accept: "application/json",
104
+ authorization: `Bearer ${registrationAccessToken}`
105
+ },
106
+ // RFC 7592 update: client_id + full metadata. Incorta applies
107
+ // redirect_uris only, but nimbus-side parsing wants the full shape.
108
+ body: JSON.stringify({
109
+ client_id: clientId,
110
+ redirect_uris: nextUris,
111
+ grant_types: current.grantTypes,
112
+ scope: current.scope,
113
+ client_name: current.name
114
+ })
115
+ });
116
+ if (!response.ok) {
117
+ await readJson(response, "Updating the client");
118
+ }
119
+ return getClient(options);
120
+ }
52
121
 
53
122
  // src/cli.ts
54
123
  var HELP = `incorta-auth \u2014 IncortaAuth SDK helper
@@ -58,8 +127,22 @@ Usage:
58
127
  --redirect <callbackUrl> [--redirect <callbackUrl> ...] \\
59
128
  [--scope "openid profile email"] [--json]
60
129
 
61
- Registers an OAuth client in Incorta (RFC 7591 dynamic registration) and prints
62
- the .env entries your app needs.
130
+ incorta-auth show --url <incortaUrl> --tenant <tenant> \\
131
+ --client <clientId> --rat <registrationAccessToken> [--json]
132
+
133
+ incorta-auth update-redirects --url <incortaUrl> --tenant <tenant> \\
134
+ --client <clientId> --rat <registrationAccessToken> \\
135
+ --redirect <callbackUrl> [--redirect ...] \\
136
+ [--replace] [--json]
137
+
138
+ register Registers an OAuth client in Incorta (RFC 7591 dynamic
139
+ registration) and prints the .env entries your app needs.
140
+ Keep the printed registration access token \u2014 it is the key
141
+ for show/update-redirects later.
142
+ show Prints a registered client's current metadata.
143
+ update-redirects Adds the given callback URLs to the client's allowed set
144
+ (or replaces the whole set with --replace, which is also how
145
+ URLs are removed).
63
146
 
64
147
  Example:
65
148
  incorta-auth register \\
@@ -69,7 +152,7 @@ Example:
69
152
  --redirect http://localhost:4000/auth/callback
70
153
  `;
71
154
  function parseArgs(argv) {
72
- const args = { redirects: [], json: false };
155
+ const args = { redirects: [], replace: false, json: false };
73
156
  for (let i = 0; i < argv.length; i++) {
74
157
  const flag = argv[i];
75
158
  const next = () => {
@@ -90,9 +173,18 @@ function parseArgs(argv) {
90
173
  case "--scope":
91
174
  args.scope = next();
92
175
  break;
176
+ case "--client":
177
+ args.client = next();
178
+ break;
179
+ case "--rat":
180
+ args.rat = next();
181
+ break;
93
182
  case "--redirect":
94
183
  args.redirects.push(next());
95
184
  break;
185
+ case "--replace":
186
+ args.replace = true;
187
+ break;
96
188
  case "--json":
97
189
  args.json = true;
98
190
  break;
@@ -102,36 +194,12 @@ function parseArgs(argv) {
102
194
  }
103
195
  return args;
104
196
  }
105
- async function main() {
106
- const [command, ...rest] = process.argv.slice(2);
107
- if (!command || command === "help" || command === "--help" || command === "-h") {
108
- console.log(HELP);
109
- return command ? 0 : 1;
110
- }
111
- if (command !== "register") {
112
- console.error(`Unknown command: ${command}
113
- `);
114
- console.log(HELP);
115
- return 1;
116
- }
117
- let args;
118
- try {
119
- args = parseArgs(rest);
120
- } catch (error) {
121
- console.error(`${error.message}
122
- `);
123
- console.log(HELP);
124
- return 1;
125
- }
126
- const missing = ["url", "tenant", "name"].filter((key) => !args[key]);
127
- if (missing.length || !args.redirects.length) {
128
- console.error(
129
- `Missing required flag(s): ${[...missing.map((m) => `--${m}`), ...args.redirects.length ? [] : ["--redirect"]].join(", ")}
130
- `
131
- );
132
- console.log(HELP);
133
- return 1;
134
- }
197
+ function requireFlags(args, flags, needsRedirect) {
198
+ const missing = flags.filter((key) => !args[key]).map((key) => `--${key}`);
199
+ if (needsRedirect && !args.redirects.length) missing.push("--redirect");
200
+ return missing;
201
+ }
202
+ async function runRegister(args) {
135
203
  const client = await registerClient({
136
204
  incortaUrl: args.url,
137
205
  tenant: args.tenant,
@@ -141,7 +209,7 @@ async function main() {
141
209
  });
142
210
  if (args.json) {
143
211
  console.log(JSON.stringify(client, null, 2));
144
- return 0;
212
+ return;
145
213
  }
146
214
  const authSecret = randomBytes(32).toString("base64url");
147
215
  console.log(`Client registered successfully.
@@ -156,10 +224,84 @@ async function main() {
156
224
  if (client.registrationAccessToken) {
157
225
  console.log(
158
226
  `
159
- # Keep these to manage the registration later (PUT/DELETE ${client.registrationClientUri ?? "the registration endpoint"}):`
227
+ # Keep this token \u2014 it manages the registration later (incorta-auth show / update-redirects):`
160
228
  );
161
229
  console.log(`# registration_access_token: ${client.registrationAccessToken}`);
162
230
  }
231
+ }
232
+ async function runShow(args) {
233
+ const info = await getClient({
234
+ incortaUrl: args.url,
235
+ tenant: args.tenant,
236
+ clientId: args.client,
237
+ registrationAccessToken: args.rat
238
+ });
239
+ if (args.json) {
240
+ console.log(JSON.stringify(info, null, 2));
241
+ return;
242
+ }
243
+ console.log(`Client: ${info.clientId}`);
244
+ if (info.name) console.log(`Name: ${info.name}`);
245
+ if (info.scope) console.log(`Scope: ${info.scope}`);
246
+ console.log(`Grant types: ${info.grantTypes.join(", ")}`);
247
+ console.log(`Redirect URIs:`);
248
+ for (const uri of info.redirectUris) console.log(` - ${uri}`);
249
+ }
250
+ async function runUpdateRedirects(args) {
251
+ const info = await updateClientRedirectUris({
252
+ incortaUrl: args.url,
253
+ tenant: args.tenant,
254
+ clientId: args.client,
255
+ registrationAccessToken: args.rat,
256
+ redirectUris: args.redirects,
257
+ mode: args.replace ? "replace" : "add"
258
+ });
259
+ if (args.json) {
260
+ console.log(JSON.stringify(info, null, 2));
261
+ return;
262
+ }
263
+ console.log(`Redirect URIs ${args.replace ? "replaced" : "updated"}. Current set:`);
264
+ for (const uri of info.redirectUris) console.log(` - ${uri}`);
265
+ }
266
+ var COMMANDS = {
267
+ register: { required: ["url", "tenant", "name"], needsRedirect: true, run: runRegister },
268
+ show: { required: ["url", "tenant", "client", "rat"], needsRedirect: false, run: runShow },
269
+ "update-redirects": {
270
+ required: ["url", "tenant", "client", "rat"],
271
+ needsRedirect: true,
272
+ run: runUpdateRedirects
273
+ }
274
+ };
275
+ async function main() {
276
+ const [command, ...rest] = process.argv.slice(2);
277
+ if (!command || command === "help" || command === "--help" || command === "-h") {
278
+ console.log(HELP);
279
+ return command ? 0 : 1;
280
+ }
281
+ const spec = COMMANDS[command];
282
+ if (!spec) {
283
+ console.error(`Unknown command: ${command}
284
+ `);
285
+ console.log(HELP);
286
+ return 1;
287
+ }
288
+ let args;
289
+ try {
290
+ args = parseArgs(rest);
291
+ } catch (error) {
292
+ console.error(`${error.message}
293
+ `);
294
+ console.log(HELP);
295
+ return 1;
296
+ }
297
+ const missing = requireFlags(args, spec.required, spec.needsRedirect);
298
+ if (missing.length) {
299
+ console.error(`Missing required flag(s): ${missing.join(", ")}
300
+ `);
301
+ console.log(HELP);
302
+ return 1;
303
+ }
304
+ await spec.run(args);
163
305
  return 0;
164
306
  }
165
307
  main().then((code) => {
package/dist/index.cjs CHANGED
@@ -22,7 +22,9 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  IncortaAuthError: () => IncortaAuthError,
24
24
  createIncortaAuth: () => createIncortaAuth,
25
- registerClient: () => registerClient
25
+ getClient: () => getClient,
26
+ registerClient: () => registerClient,
27
+ updateClientRedirectUris: () => updateClientRedirectUris
26
28
  });
27
29
  module.exports = __toCommonJS(src_exports);
28
30
 
@@ -581,6 +583,26 @@ function createHandler(config) {
581
583
  }
582
584
 
583
585
  // src/core/register.ts
586
+ function registrationEndpoint(incortaUrl, tenant, clientId) {
587
+ const base = `${incortaUrl.replace(/\/+$/, "")}/oauth/${tenant}/register`;
588
+ return clientId ? `${base}/${clientId}` : base;
589
+ }
590
+ async function readJson(response, what) {
591
+ let body;
592
+ try {
593
+ body = await response.json();
594
+ } catch {
595
+ throw new Error(
596
+ `[incorta-auth] ${what} returned a non-JSON response (HTTP ${response.status}). Is the OAuth authorization server enabled on this cluster?`
597
+ );
598
+ }
599
+ if (!response.ok) {
600
+ throw new Error(
601
+ `[incorta-auth] ${what} failed (HTTP ${response.status}): ${body.error ?? "unknown_error"} \u2014 ${body.error_description ?? "no description"}`
602
+ );
603
+ }
604
+ return body;
605
+ }
584
606
  async function registerClient(options) {
585
607
  const {
586
608
  incortaUrl,
@@ -594,8 +616,7 @@ async function registerClient(options) {
594
616
  if (!redirectUris.length) {
595
617
  throw new Error("[incorta-auth] registerClient requires at least one redirect URI.");
596
618
  }
597
- const endpoint = `${incortaUrl.replace(/\/+$/, "")}/oauth/${tenant}/register`;
598
- const response = await fetchImpl(endpoint, {
619
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant), {
599
620
  method: "POST",
600
621
  headers: { "content-type": "application/json", accept: "application/json" },
601
622
  body: JSON.stringify({
@@ -606,17 +627,10 @@ async function registerClient(options) {
606
627
  token_endpoint_auth_method: "client_secret_basic"
607
628
  })
608
629
  });
609
- let body;
610
- try {
611
- body = await response.json();
612
- } catch {
630
+ const body = await readJson(response, "Client registration");
631
+ if (!body.client_id || !body.client_secret) {
613
632
  throw new Error(
614
- `[incorta-auth] Client registration returned a non-JSON response (HTTP ${response.status}). Is the OAuth authorization server enabled on this cluster?`
615
- );
616
- }
617
- if (!response.ok || !body.client_id || !body.client_secret) {
618
- throw new Error(
619
- `[incorta-auth] Client registration failed (HTTP ${response.status}): ${body.error ?? "unknown_error"} \u2014 ${body.error_description ?? "no description"}`
633
+ `[incorta-auth] Client registration failed: response is missing client_id/client_secret.`
620
634
  );
621
635
  }
622
636
  return {
@@ -626,6 +640,63 @@ async function registerClient(options) {
626
640
  registrationClientUri: body.registration_client_uri
627
641
  };
628
642
  }
643
+ function toClientInfo(body, fallbackClientId) {
644
+ return {
645
+ clientId: body.client_id ?? fallbackClientId,
646
+ name: body.client_name,
647
+ redirectUris: body.redirect_uris ?? [],
648
+ grantTypes: body.grant_types ?? [],
649
+ scope: body.scope
650
+ };
651
+ }
652
+ async function getClient(options) {
653
+ const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;
654
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
655
+ headers: {
656
+ accept: "application/json",
657
+ authorization: `Bearer ${registrationAccessToken}`
658
+ }
659
+ });
660
+ const body = await readJson(response, "Reading the client");
661
+ return toClientInfo(body, clientId);
662
+ }
663
+ async function updateClientRedirectUris(options) {
664
+ const {
665
+ incortaUrl,
666
+ tenant,
667
+ clientId,
668
+ registrationAccessToken,
669
+ redirectUris,
670
+ mode = "add",
671
+ fetchImpl = fetch
672
+ } = options;
673
+ if (!redirectUris.length) {
674
+ throw new Error("[incorta-auth] updateClientRedirectUris requires at least one redirect URI.");
675
+ }
676
+ const current = await getClient(options);
677
+ const nextUris = mode === "add" ? [.../* @__PURE__ */ new Set([...current.redirectUris, ...redirectUris])] : redirectUris;
678
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
679
+ method: "PUT",
680
+ headers: {
681
+ "content-type": "application/json",
682
+ accept: "application/json",
683
+ authorization: `Bearer ${registrationAccessToken}`
684
+ },
685
+ // RFC 7592 update: client_id + full metadata. Incorta applies
686
+ // redirect_uris only, but nimbus-side parsing wants the full shape.
687
+ body: JSON.stringify({
688
+ client_id: clientId,
689
+ redirect_uris: nextUris,
690
+ grant_types: current.grantTypes,
691
+ scope: current.scope,
692
+ client_name: current.name
693
+ })
694
+ });
695
+ if (!response.ok) {
696
+ await readJson(response, "Updating the client");
697
+ }
698
+ return getClient(options);
699
+ }
629
700
 
630
701
  // src/index.ts
631
702
  function createIncortaAuth(config = {}) {
@@ -637,6 +708,8 @@ function createIncortaAuth(config = {}) {
637
708
  0 && (module.exports = {
638
709
  IncortaAuthError,
639
710
  createIncortaAuth,
640
- registerClient
711
+ getClient,
712
+ registerClient,
713
+ updateClientRedirectUris
641
714
  });
642
715
  //# sourceMappingURL=index.cjs.map