@gjermundgaraba/clankerauth 0.27.0 → 0.28.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.
Files changed (3) hide show
  1. package/README.md +14 -2
  2. package/dist/main.mjs +27 -10
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @gjermundgaraba/clankerauth
2
2
 
3
- The `clankerauth` command: sign in to a [clankerauth](https://github.com/gjermundgaraba/clankerauth) issuer as its owner, run every administration action over HTTP, and hand other commands access tokens for the issuer's resources. It needs Node 26 or newer and an issuer at 0.24.0 or later.
3
+ The `clankerauth` command: sign in to a [clankerauth](https://github.com/gjermundgaraba/clankerauth) issuer as its owner, run every administration action over HTTP, and hand other commands access tokens for the issuer's resources. It needs Node 26 or newer and an issuer at 0.24.0 or later, or 0.28.0 for an administration key in `CLANKERAUTH_TOKEN`.
4
4
 
5
5
  ```sh
6
6
  npm install --global @gjermundgaraba/clankerauth
@@ -16,7 +16,19 @@ clankerauth logout
16
16
 
17
17
  The sign-ins are kept in `$XDG_CONFIG_HOME/clankerauth/sign-ins.json`, `~/.config/clankerauth/sign-ins.json` by default, readable by their owner alone: one per resource, each with its own issuer, so signing in at a second issuer leaves the first's alone. A file the command cannot read, such as one another version wrote, counts as signed out and the next `login` replaces it. A `login` to a resource already signed in to replaces that sign-in and revokes it. A command refreshes an access token as it is about to expire and saves the rotated refresh token, so a sign-in lasts while it is used at least every 30 days; when the issuer refuses a refresh, the error names the `login` that asks for the same scopes again. Every change to the file holds its lock, so commands and programs running at once refresh an expired sign-in once, and `logout` revokes the refresh token that is current. `logout` revokes every sign-in and deletes the file, and `logout <url>` the one whose resource that URL is under alone.
18
18
 
19
- Administration commands act at the issuer of the one administration sign-in kept. Signed in to administration at several issuers, name one with `CLANKERAUTH_URL`, its origin; a value that is no URL is refused.
19
+ Administration commands act at the issuer of the one administration sign-in kept. Signed in to administration at several issuers, name one with `CLANKERAUTH_URL`, its origin; a value that names no origin, such as a `mailto:` URL, is refused.
20
+
21
+ Unattended, such as in a container with no browser or disk to keep a sign-in on, the commands take an administration key, an API key granted on the issuer's origin root, from `CLANKERAUTH_TOKEN` instead, and then act at the issuer `CLANKERAUTH_URL` names, which they require. No sign-in is read or written. The owner creates the key once, signed in:
22
+
23
+ ```sh
24
+ clankerauth create-api-key --name deployer --expires-at null \
25
+ --permissions '{"https://clankerauth.home.example/":["clankerauth:read","clankerauth:write"]}'
26
+
27
+ CLANKERAUTH_URL=https://clankerauth.home.example CLANKERAUTH_TOKEN=clankerauth_… \
28
+ clankerauth list-api-keys
29
+ ```
30
+
31
+ What the key may do, and when a change to it applies, is in [the issuer's README](../../README.md#administer-unattended-with-an-api-key).
20
32
 
21
33
  ## Tokens for other commands
22
34
 
package/dist/main.mjs CHANGED
@@ -400,7 +400,7 @@ const OwnerHttp = ActionHttp.make(Administration, {
400
400
  });
401
401
  //#endregion
402
402
  //#region package.json
403
- var version = "0.27.0";
403
+ var version = "0.28.1";
404
404
  //#endregion
405
405
  //#region src/main.ts
406
406
  const refused = (error) => new CliError.UserError({
@@ -437,15 +437,15 @@ const notSignedIn = new CliError.UserError({
437
437
  cause: "not signed in",
438
438
  userMessage: "Not signed in: run `clankerauth login <url>` first."
439
439
  });
440
+ const issuerNamed = Config.option(Config.String("CLANKERAUTH_URL")).pipe(Effect.mapError(refused), Effect.map(Option.map((url) => URL.parse(url)?.origin ?? "null")), Effect.flatMap((origin) => Option.contains(origin, "null") ? Effect.fail(new CliError.UserError({
441
+ cause: "CLANKERAUTH_URL",
442
+ userMessage: "CLANKERAUTH_URL is no issuer URL: set it to the issuer's origin, its CLANKERAUTH_BASE_URL."
443
+ })) : Effect.succeed(origin)));
440
444
  const administrationSignIn = Effect.gen(function* () {
441
- const named = yield* Config.option(Config.String("CLANKERAUTH_URL")).pipe(Effect.mapError(refused));
442
- if (Option.exists(named, (url) => !URL.canParse(url))) return yield* new CliError.UserError({
443
- cause: "CLANKERAUTH_URL",
444
- userMessage: "CLANKERAUTH_URL is no URL: set it to the issuer's origin, its CLANKERAUTH_BASE_URL."
445
- });
445
+ const named = yield* issuerNamed;
446
446
  const kept = (yield* Credentials.list().pipe(Effect.mapError(refused))).filter((signIn) => signIn.resource === administrationIdentifier(signIn.url)).filter((signIn) => Option.match(named, {
447
447
  onNone: () => true,
448
- onSome: (url) => new URL(url).origin === signIn.url
448
+ onSome: (origin) => origin === signIn.url
449
449
  }));
450
450
  const [only, ...others] = kept;
451
451
  if (only === void 0) return yield* notSignedIn;
@@ -455,14 +455,31 @@ const administrationSignIn = Effect.gen(function* () {
455
455
  });
456
456
  return only;
457
457
  });
458
+ const administrationKey = Config.Redacted("CLANKERAUTH_TOKEN");
459
+ const administrationTarget = Effect.gen(function* () {
460
+ const given = yield* Config.option(administrationKey).pipe(Effect.mapError(refused));
461
+ if (Option.isNone(given)) return yield* administrationSignIn;
462
+ const named = yield* issuerNamed;
463
+ if (Option.isNone(named)) return yield* new CliError.UserError({
464
+ cause: "CLANKERAUTH_URL",
465
+ userMessage: "CLANKERAUTH_TOKEN is set without CLANKERAUTH_URL: set it to the issuer's origin, its CLANKERAUTH_BASE_URL."
466
+ });
467
+ return {
468
+ url: named.value,
469
+ resource: administrationIdentifier(named.value)
470
+ };
471
+ });
458
472
  const tokenCommand = Command.make("token", { url: urlArgument("An address under the resource whose token to print, such as the application's URL; administration by default") }, ({ url }) => Effect.gen(function* () {
459
473
  const named = Option.isSome(url) ? url.value : (yield* administrationSignIn).resource;
460
474
  const token = yield* Credentials.token({ url: named }).pipe(Effect.mapError(refused));
461
475
  yield* Effect.sync(() => process.stdout.write(`${Redacted.value(token)}\n`));
462
476
  })).pipe(Command.withDescription("Print a current access token for the resource signed in to that a URL is under, refreshing it first when it has expired."));
463
- const administrationClient = Effect.flatMap(administrationSignIn, (signIn) => ActionHttp.client(OwnerHttp, {
464
- baseUrl: signIn.url,
465
- transformClient: (client) => HttpClient.catchTag(Credentials.authorizing({ url: signIn.resource })(client), ["SignInRequired", "TokenUnavailable"], (error) => Effect.fail(refused(error)))
477
+ const administrationClient = Effect.flatMap(administrationTarget, (target) => ActionHttp.client(OwnerHttp, {
478
+ baseUrl: target.url,
479
+ transformClient: (client) => HttpClient.catchTag(Credentials.authorizing({
480
+ url: target.resource,
481
+ token: administrationKey
482
+ })(client), ["SignInRequired", "TokenUnavailable"], (error) => Effect.fail(refused(error)))
466
483
  }));
467
484
  const administration = Administration.map((action) => ActionCli.remoteCommand(OwnerHttp, action, { client: administrationClient }));
468
485
  const cli = Command.make("clankerauth").pipe(Command.withDescription("Administer a clankerauth issuer as its owner, and hand other commands its tokens. Results are JSON on stdout; a created key or client secret is in it once."), Command.withSubcommands([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/clankerauth",
3
- "version": "0.27.0",
3
+ "version": "0.28.1",
4
4
  "description": "The clankerauth command line: sign in to an issuer as its owner and administer it over HTTP.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,13 +21,13 @@
21
21
  "dependencies": {
22
22
  "@effect/platform-node": "4.0.5",
23
23
  "@effect/platform-node-shared": "4.0.5",
24
- "@gjermundgaraba/clankerauth-sdk": "0.27.0",
24
+ "@gjermundgaraba/clankerauth-sdk": "0.28.1",
25
25
  "@gjermundgaraba/effect-actions": "0.19.0",
26
26
  "effect": "4.0.5"
27
27
  },
28
28
  "devDependencies": {
29
- "@gjermundgaraba/clankerauth-admin-api": "0.27.0",
30
- "@gjermundgaraba/clankerauth-dev": "0.27.0",
29
+ "@gjermundgaraba/clankerauth-admin-api": "0.28.1",
30
+ "@gjermundgaraba/clankerauth-dev": "0.28.1",
31
31
  "@types/node": "26.6.5",
32
32
  "typescript": "7.0.2",
33
33
  "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",