@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.cjs CHANGED
@@ -29,9 +29,11 @@ function clientSchema() {
29
29
  {
30
30
  clientId: { type: String, required: true, unique: true },
31
31
  name: { type: String, required: true },
32
- // `public` exists for CIMD clients, which hold no secret and are bound by
33
- // PKCE instead. The enum was written with this widening in mind, so it
34
- // was one member rather than a migration.
32
+ // `public` is a client that holds no secret and is bound by PKCE instead.
33
+ // Two ways in and they are independent: a CIMD row is written public, and
34
+ // `clients.create({ type: 'public' })` registers one by hand. The enum was
35
+ // written with this widening in mind, so it was one member rather than a
36
+ // migration.
35
37
  type: { type: String, enum: ["confidential", "public"], default: "confidential" },
36
38
  // How the row got here. A `cimd` row is re-derived from the client's own
37
39
  // metadata document, so this is what tells the re-fetch path which fields
@@ -1364,6 +1366,15 @@ function assertResources(ctx, input) {
1364
1366
  }
1365
1367
  return [...input];
1366
1368
  }
1369
+ function assertClientType(value) {
1370
+ if (value === void 0) return "confidential";
1371
+ if (value !== "confidential" && value !== "public") {
1372
+ throw new TypeError(
1373
+ `oauth-host: client type must be 'confidential' or 'public' (got ${JSON.stringify(value)})`
1374
+ );
1375
+ }
1376
+ return value;
1377
+ }
1367
1378
  function assertName(value) {
1368
1379
  if (typeof value !== "string" || !value.trim()) {
1369
1380
  throw new TypeError("oauth-host: a client needs a non-empty `name` \u2014 it is what the consent screen shows");
@@ -1385,35 +1396,38 @@ async function revokeTokensMatching(ctx, filter) {
1385
1396
  return res.modifiedCount ?? 0;
1386
1397
  }
1387
1398
  function createClientsApi(ctx) {
1399
+ async function create(spec) {
1400
+ if (!spec || typeof spec !== "object") {
1401
+ throw new TypeError("oauth-host: clients.create(spec) requires a spec object");
1402
+ }
1403
+ const name = assertName(spec.name);
1404
+ const type = assertClientType(spec.type);
1405
+ const redirectUris = assertRedirectUris(spec.redirectUris);
1406
+ const allowedScopes = assertScopes(ctx, spec.allowedScopes);
1407
+ const allowedResources = assertResources(ctx, spec.allowedResources);
1408
+ const clientId = spec.clientId ?? generateClientId();
1409
+ const clientSecret = type === "confidential" ? generateClientSecret() : void 0;
1410
+ const doc = await ctx.models.Client.create({
1411
+ clientId,
1412
+ name,
1413
+ type,
1414
+ registration: "manual",
1415
+ trusted: Boolean(spec.trusted),
1416
+ // Only the digest is stored. `clientSecret` below is the only time the
1417
+ // raw value exists outside the caller's variable. Empty for a public
1418
+ // client, which is the same shape a CIMD row is written with.
1419
+ secrets: clientSecret ? [{ hash: sha256(clientSecret), label: "initial", createdAt: /* @__PURE__ */ new Date() }] : [],
1420
+ redirectUris,
1421
+ allowedScopes,
1422
+ allowedResources,
1423
+ branding: spec.branding ?? {},
1424
+ status: "active"
1425
+ });
1426
+ await ctx.audit({ type: "oauth.client_created", actor: "admin", clientId, meta: { type } });
1427
+ return clientSecret ? { client: toPublicClient(doc), clientId, type: "confidential", clientSecret } : { client: toPublicClient(doc), clientId, type: "public" };
1428
+ }
1388
1429
  return {
1389
- async create(spec) {
1390
- if (!spec || typeof spec !== "object") {
1391
- throw new TypeError("oauth-host: clients.create(spec) requires a spec object");
1392
- }
1393
- const name = assertName(spec.name);
1394
- const redirectUris = assertRedirectUris(spec.redirectUris);
1395
- const allowedScopes = assertScopes(ctx, spec.allowedScopes);
1396
- const allowedResources = assertResources(ctx, spec.allowedResources);
1397
- const clientId = spec.clientId ?? generateClientId();
1398
- const clientSecret = generateClientSecret();
1399
- const doc = await ctx.models.Client.create({
1400
- clientId,
1401
- name,
1402
- type: "confidential",
1403
- registration: "manual",
1404
- trusted: Boolean(spec.trusted),
1405
- // Only the digest is stored. `clientSecret` below is the only time the
1406
- // raw value exists outside the caller's variable.
1407
- secrets: [{ hash: sha256(clientSecret), label: "initial", createdAt: /* @__PURE__ */ new Date() }],
1408
- redirectUris,
1409
- allowedScopes,
1410
- allowedResources,
1411
- branding: spec.branding ?? {},
1412
- status: "active"
1413
- });
1414
- await ctx.audit({ type: "oauth.client_created", actor: "admin", clientId });
1415
- return { client: toPublicClient(doc), clientId, clientSecret };
1416
- },
1430
+ create,
1417
1431
  async rotateSecret(clientId, opts = {}) {
1418
1432
  const doc = await ctx.models.Client.findOne({ clientId });
1419
1433
  if (!doc) throw notFound(clientId);
@@ -1442,7 +1456,7 @@ function createClientsApi(ctx) {
1442
1456
  clientId,
1443
1457
  meta: { retireAfter, retiresAt }
1444
1458
  });
1445
- return { client: toPublicClient(doc), clientId, clientSecret };
1459
+ return { client: toPublicClient(doc), clientId, type: "confidential", clientSecret };
1446
1460
  },
1447
1461
  async update(clientId, patch) {
1448
1462
  const doc = await ctx.models.Client.findOne({ clientId });