@jeffjassky/oauth-host 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -22,9 +22,11 @@ function clientSchema() {
22
22
  {
23
23
  clientId: { type: String, required: true, unique: true },
24
24
  name: { type: String, required: true },
25
- // `public` exists for CIMD clients, which hold no secret and are bound by
26
- // PKCE instead. The enum was written with this widening in mind, so it
27
- // was one member rather than a migration.
25
+ // `public` is a client that holds no secret and is bound by PKCE instead.
26
+ // Two ways in and they are independent: a CIMD row is written public, and
27
+ // `clients.create({ type: 'public' })` registers one by hand. The enum was
28
+ // written with this widening in mind, so it was one member rather than a
29
+ // migration.
28
30
  type: { type: String, enum: ["confidential", "public"], default: "confidential" },
29
31
  // How the row got here. A `cimd` row is re-derived from the client's own
30
32
  // metadata document, so this is what tells the re-fetch path which fields
@@ -1357,6 +1359,15 @@ function assertResources(ctx, input) {
1357
1359
  }
1358
1360
  return [...input];
1359
1361
  }
1362
+ function assertClientType(value) {
1363
+ if (value === void 0) return "confidential";
1364
+ if (value !== "confidential" && value !== "public") {
1365
+ throw new TypeError(
1366
+ `oauth-host: client type must be 'confidential' or 'public' (got ${JSON.stringify(value)})`
1367
+ );
1368
+ }
1369
+ return value;
1370
+ }
1360
1371
  function assertName(value) {
1361
1372
  if (typeof value !== "string" || !value.trim()) {
1362
1373
  throw new TypeError("oauth-host: a client needs a non-empty `name` \u2014 it is what the consent screen shows");
@@ -1378,35 +1389,38 @@ async function revokeTokensMatching(ctx, filter) {
1378
1389
  return res.modifiedCount ?? 0;
1379
1390
  }
1380
1391
  function createClientsApi(ctx) {
1392
+ async function create(spec) {
1393
+ if (!spec || typeof spec !== "object") {
1394
+ throw new TypeError("oauth-host: clients.create(spec) requires a spec object");
1395
+ }
1396
+ const name = assertName(spec.name);
1397
+ const type = assertClientType(spec.type);
1398
+ const redirectUris = assertRedirectUris(spec.redirectUris);
1399
+ const allowedScopes = assertScopes(ctx, spec.allowedScopes);
1400
+ const allowedResources = assertResources(ctx, spec.allowedResources);
1401
+ const clientId = spec.clientId ?? generateClientId();
1402
+ const clientSecret = type === "confidential" ? generateClientSecret() : void 0;
1403
+ const doc = await ctx.models.Client.create({
1404
+ clientId,
1405
+ name,
1406
+ type,
1407
+ registration: "manual",
1408
+ trusted: Boolean(spec.trusted),
1409
+ // Only the digest is stored. `clientSecret` below is the only time the
1410
+ // raw value exists outside the caller's variable. Empty for a public
1411
+ // client, which is the same shape a CIMD row is written with.
1412
+ secrets: clientSecret ? [{ hash: sha256(clientSecret), label: "initial", createdAt: /* @__PURE__ */ new Date() }] : [],
1413
+ redirectUris,
1414
+ allowedScopes,
1415
+ allowedResources,
1416
+ branding: spec.branding ?? {},
1417
+ status: "active"
1418
+ });
1419
+ await ctx.audit({ type: "oauth.client_created", actor: "admin", clientId, meta: { type } });
1420
+ return clientSecret ? { client: toPublicClient(doc), clientId, type: "confidential", clientSecret } : { client: toPublicClient(doc), clientId, type: "public" };
1421
+ }
1381
1422
  return {
1382
- async create(spec) {
1383
- if (!spec || typeof spec !== "object") {
1384
- throw new TypeError("oauth-host: clients.create(spec) requires a spec object");
1385
- }
1386
- const name = assertName(spec.name);
1387
- const redirectUris = assertRedirectUris(spec.redirectUris);
1388
- const allowedScopes = assertScopes(ctx, spec.allowedScopes);
1389
- const allowedResources = assertResources(ctx, spec.allowedResources);
1390
- const clientId = spec.clientId ?? generateClientId();
1391
- const clientSecret = generateClientSecret();
1392
- const doc = await ctx.models.Client.create({
1393
- clientId,
1394
- name,
1395
- type: "confidential",
1396
- registration: "manual",
1397
- trusted: Boolean(spec.trusted),
1398
- // Only the digest is stored. `clientSecret` below is the only time the
1399
- // raw value exists outside the caller's variable.
1400
- secrets: [{ hash: sha256(clientSecret), label: "initial", createdAt: /* @__PURE__ */ new Date() }],
1401
- redirectUris,
1402
- allowedScopes,
1403
- allowedResources,
1404
- branding: spec.branding ?? {},
1405
- status: "active"
1406
- });
1407
- await ctx.audit({ type: "oauth.client_created", actor: "admin", clientId });
1408
- return { client: toPublicClient(doc), clientId, clientSecret };
1409
- },
1423
+ create,
1410
1424
  async rotateSecret(clientId, opts = {}) {
1411
1425
  const doc = await ctx.models.Client.findOne({ clientId });
1412
1426
  if (!doc) throw notFound(clientId);
@@ -1435,7 +1449,7 @@ function createClientsApi(ctx) {
1435
1449
  clientId,
1436
1450
  meta: { retireAfter, retiresAt }
1437
1451
  });
1438
- return { client: toPublicClient(doc), clientId, clientSecret };
1452
+ return { client: toPublicClient(doc), clientId, type: "confidential", clientSecret };
1439
1453
  },
1440
1454
  async update(clientId, patch) {
1441
1455
  const doc = await ctx.models.Client.findOne({ clientId });