@gjermundgaraba/clankerauth 0.24.0 → 0.25.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/README.md +1 -1
- package/dist/main.mjs +22 -6
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ clankerauth create-api-key --name "notes sync" \
|
|
|
12
12
|
clankerauth logout
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`login <url>` signs in to the resource `url` identifies, reading it from its `/.well-known/oauth-protected-resource`: the issuer's own origin, `CLANKERAUTH_BASE_URL`, publishes administration, and an application's URL publishes its resource. It then discovers the issuer that resource names; registers the command there as a native OAuth client, a new one for each sign-in;
|
|
15
|
+
`login <url>` signs in to the resource `url` identifies, reading it from its `/.well-known/oauth-protected-resource`: the issuer's own origin, `CLANKERAUTH_BASE_URL`, publishes administration, and an application's URL publishes its resource. It then discovers the issuer that resource names; registers the command there as a native OAuth client, a new one for each sign-in; opens the approval page in the browser and prints its URL on stderr, for a terminal with no browser to open, or alone with `--no-browser`; and listens for the redirect on a loopback port. It asks for every scope the resource publishes, `clankerauth:read clankerauth:write` for administration, unless `--scope` names fewer: `--scope clankerauth:read` lists and never changes. Beside them it asks for `offline_access`, alone for a resource that publishes no scopes. A resource must publish that document, and so be identified by an `http:` or `https:` URL, to be signed in to.
|
|
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
|
|
package/dist/main.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { Config, Console, Context, Effect, Option, Redacted, Schema } from "effect";
|
|
3
4
|
import { Argument, CliError, Command, Flag } from "effect/cli";
|
|
4
5
|
import { FetchHttpClient, HttpClient } from "effect/http";
|
|
@@ -399,7 +400,7 @@ const OwnerHttp = ActionHttp.make(Administration, {
|
|
|
399
400
|
});
|
|
400
401
|
//#endregion
|
|
401
402
|
//#region package.json
|
|
402
|
-
var version = "0.
|
|
403
|
+
var version = "0.25.0";
|
|
403
404
|
//#endregion
|
|
404
405
|
//#region src/main.ts
|
|
405
406
|
const refused = (error) => new CliError.UserError({
|
|
@@ -408,14 +409,29 @@ const refused = (error) => new CliError.UserError({
|
|
|
408
409
|
});
|
|
409
410
|
const json = (value) => Console.log(JSON.stringify(value, null, 2));
|
|
410
411
|
const urlArgument = (description) => Argument.String("url").pipe(Argument.withDescription(description), Argument.optional);
|
|
411
|
-
const
|
|
412
|
+
const browserOpener = (url) => {
|
|
413
|
+
switch (process.platform) {
|
|
414
|
+
case "darwin": return ["open", [url]];
|
|
415
|
+
case "win32": return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
416
|
+
default: return ["xdg-open", [url]];
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const openInBrowser = (page) => Effect.sync(() => {
|
|
420
|
+
const [command, args] = browserOpener(page.href);
|
|
421
|
+
spawn(command, args, {
|
|
422
|
+
stdio: "ignore",
|
|
423
|
+
detached: true
|
|
424
|
+
}).on("error", () => void 0).unref();
|
|
425
|
+
});
|
|
426
|
+
const approve = (browser) => (page) => browser ? Effect.andThen(Effect.sync(() => process.stderr.write(`Approve the sign-in in your browser. If it did not open, open this URL:\n\n ${page.href}\n\n`)), openInBrowser(page)) : Effect.sync(() => process.stderr.write(`Open this URL in your browser to approve the sign-in:\n\n ${page.href}\n\n`));
|
|
412
427
|
const loginCommand = Command.make("login", {
|
|
413
428
|
url: Argument.String("url").pipe(Argument.withDescription("The resource to sign in to: an application's URL, such as https://notes.example, or the issuer's origin, its CLANKERAUTH_BASE_URL, for administration")),
|
|
414
|
-
scope: Flag.String("scope").pipe(Flag.withDescription("The scopes to ask for, space-separated: every scope the resource publishes by default"), Flag.optional)
|
|
415
|
-
|
|
429
|
+
scope: Flag.String("scope").pipe(Flag.withDescription("The scopes to ask for, space-separated: every scope the resource publishes by default"), Flag.optional),
|
|
430
|
+
noBrowser: Flag.Boolean("no-browser").pipe(Flag.withDescription("Print the approval URL without opening it in the browser"), Flag.withDefault(false))
|
|
431
|
+
}, ({ url, scope, noBrowser }) => Credentials.signIn(url, {
|
|
416
432
|
scope: Option.getOrUndefined(scope),
|
|
417
|
-
approve
|
|
418
|
-
}).pipe(Effect.mapError(refused), Effect.flatMap(json))).pipe(Command.withDescription("Sign in as the issuer's owner to a resource: approve the
|
|
433
|
+
approve: approve(!noBrowser)
|
|
434
|
+
}).pipe(Effect.mapError(refused), Effect.flatMap(json))).pipe(Command.withDescription("Sign in as the issuer's owner to a resource: approve the page it opens in the browser, or the URL it prints. The resource's issuer and scopes are read from its /.well-known/oauth-protected-resource. Sign-ins are kept in ~/.config/clankerauth, each beside the others, whatever its issuer."));
|
|
419
435
|
const logoutCommand = Command.make("logout", { url: urlArgument("Sign out of the resource this URL is under alone") }, ({ url }) => Credentials.signOut(Option.getOrUndefined(url)).pipe(Effect.mapError(refused), Effect.flatMap(json))).pipe(Command.withDescription("Revoke the sign-ins, or the one to the resource a URL is under, and forget them."));
|
|
420
436
|
const notSignedIn = new CliError.UserError({
|
|
421
437
|
cause: "not signed in",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba/clankerauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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": {
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@effect/platform-node": "4.0.
|
|
23
|
-
"@effect/platform-node-shared": "4.0.
|
|
24
|
-
"@gjermundgaraba/clankerauth-sdk": "0.
|
|
25
|
-
"@gjermundgaraba/effect-actions": "0.18.
|
|
26
|
-
"effect": "4.0.
|
|
22
|
+
"@effect/platform-node": "4.0.5",
|
|
23
|
+
"@effect/platform-node-shared": "4.0.5",
|
|
24
|
+
"@gjermundgaraba/clankerauth-sdk": "0.25.0",
|
|
25
|
+
"@gjermundgaraba/effect-actions": "0.18.2",
|
|
26
|
+
"effect": "4.0.5"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@gjermundgaraba/clankerauth-admin-api": "0.
|
|
30
|
-
"@gjermundgaraba/clankerauth-dev": "0.
|
|
31
|
-
"@types/node": "26.6.
|
|
29
|
+
"@gjermundgaraba/clankerauth-admin-api": "0.25.0",
|
|
30
|
+
"@gjermundgaraba/clankerauth-dev": "0.25.0",
|
|
31
|
+
"@types/node": "26.6.5",
|
|
32
32
|
"typescript": "7.0.2",
|
|
33
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
34
|
-
"vite-plus": "1.
|
|
33
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
|
|
34
|
+
"vite-plus": "1.3.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=26"
|