@pidgeon-ai/issuer 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +121 -2
- package/dist/bin.js +24 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-CCOFMK5K.js +1429 -0
- package/dist/chunk-CCOFMK5K.js.map +1 -0
- package/dist/index.d.ts +307 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PCN Labs OÜ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pidgeon-ai/issuer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Post issuer you run yourself, inside your own network. Your own domain
|
|
4
|
+
signs your AI agents' cards, sign-ins and grants, with a key that never
|
|
5
|
+
leaves you, for the agents your identity provider says you have. Nothing is
|
|
6
|
+
sent to Pidgeon: not the key, not the list of agents.
|
|
7
|
+
|
|
8
|
+
It is for companies that may not hand authentication to a third party. Pidgeon
|
|
9
|
+
Cloud runs the same issuer for everyone else. Both speak the same protocol, so
|
|
10
|
+
`@pidgeon-ai/id` (the agent's side) and `@pidgeon-ai/sign-in` (the site's)
|
|
11
|
+
work with either one unchanged.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @pidgeon-ai/issuer keygen acme.com
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What runs where
|
|
18
|
+
|
|
19
|
+
| Where | What |
|
|
20
|
+
| ---------------------- | -------------------------------------------------------------------------- |
|
|
21
|
+
| Inside your network | The issuer (`pidgeon-issuer serve`, or the container), its key, its config |
|
|
22
|
+
| Your identity provider | Who the agents are: their addresses, keys and groups |
|
|
23
|
+
| `post.<your domain>` | The public documents: your keys, your agents' cards, OpenID discovery |
|
|
24
|
+
| `_post.<your domain>` | A TXT record, so verifiers find `post.<your domain>` |
|
|
25
|
+
| Each agent | Its own Ed25519 key, which signs its mail, its requests and its sign-ins |
|
|
26
|
+
|
|
27
|
+
The token endpoint only needs to reach your agents. Everything a stranger
|
|
28
|
+
reads is a document: publish it through your reverse proxy, or write it out
|
|
29
|
+
with `pidgeon-issuer export <dir>` and serve it from any static host.
|
|
30
|
+
|
|
31
|
+
## Run it
|
|
32
|
+
|
|
33
|
+
1. **The issuer's key**, once: `pidgeon-issuer keygen acme.com`. It writes
|
|
34
|
+
`pidgeon-issuer-key.json` (mode 0600). Mount it as a secret, or put the
|
|
35
|
+
seed in `PIDGEON_ISSUER_KEY`. Any `Signer` fits in its place, so a KMS or
|
|
36
|
+
an HSM needs no change to the rest.
|
|
37
|
+
2. **Each agent's key**, on the agent's machine: `pidgeon-issuer keygen
|
|
38
|
+
acme.com --out ~/.config/pidgeon/id/acme.com.json`. Give the identity
|
|
39
|
+
provider the `key` line it prints; the private half stays with the agent.
|
|
40
|
+
3. **`issuer.json`**: copy `issuer.example.json`. `directory.keycloak` reads
|
|
41
|
+
the agents from Keycloak (below); `directory.static` lists them by hand. `grants` maps a group to what its agents may do, signed by
|
|
42
|
+
your domain as the organisation. `clients` are the sites you let your
|
|
43
|
+
agents sign in to.
|
|
44
|
+
4. **DNS**: `pidgeon-issuer dns` prints the `_post` record and where
|
|
45
|
+
`post.acme.com` must answer.
|
|
46
|
+
5. **Serve**: `pidgeon-issuer serve` (port `PORT`, default 8080), or
|
|
47
|
+
`docker build -f packages/issuer/Dockerfile -t pidgeon-issuer .` from this
|
|
48
|
+
repository's root and run it with the config and the key mounted at
|
|
49
|
+
`/etc/pidgeon-issuer/`.
|
|
50
|
+
|
|
51
|
+
## Keycloak
|
|
52
|
+
|
|
53
|
+
An agent is a Keycloak client with a service account, in the `agents` group.
|
|
54
|
+
Its key is the Ed25519 key Keycloak authenticates it with (Credentials →
|
|
55
|
+
Signed JWT, EdDSA), so the agent holds one key for Keycloak and for sites; a
|
|
56
|
+
client on a secret names its key in a `pidgeon.key` attribute instead.
|
|
57
|
+
`pidgeon.address` (default `<clientId>@<domain>`) and `pidgeon.kind` say the
|
|
58
|
+
rest, and its groups are what `grants` maps.
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
"directory": {
|
|
62
|
+
"keycloak": {
|
|
63
|
+
"url": "https://sso.acme.internal",
|
|
64
|
+
"realm": "acme",
|
|
65
|
+
"client_id": "pidgeon-issuer",
|
|
66
|
+
"group": "agents"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`pidgeon-issuer` is a client of its own with a service account holding
|
|
72
|
+
`realm-management`'s `view-clients` and `view-users`, nothing that writes; its
|
|
73
|
+
secret is `KEYCLOAK_CLIENT_SECRET`. `examples/self-hosted-issuer` runs all of
|
|
74
|
+
it in Docker.
|
|
75
|
+
|
|
76
|
+
## Sign in to a site
|
|
77
|
+
|
|
78
|
+
A site lists your issuer beside Pidgeon's:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { pidgeonSignIn } from '@pidgeon-ai/sign-in';
|
|
82
|
+
|
|
83
|
+
export const pidgeon = pidgeonSignIn({
|
|
84
|
+
clientId: 'client_shop',
|
|
85
|
+
url: 'https://shop.example',
|
|
86
|
+
secret: process.env.PIDGEON_SESSION_SECRET!,
|
|
87
|
+
issuers: ['https://post.acme.com'],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
and your agent signs in as it would through Pidgeon Cloud: its card comes from
|
|
92
|
+
`post.acme.com`, and it goes to the issuer that signed the card.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { loadIdentity, signInWithPidgeon } from '@pidgeon-ai/id';
|
|
96
|
+
|
|
97
|
+
const { site } = await signInWithPidgeon(await loadIdentity('acme.com'), {
|
|
98
|
+
agent: 'support-agent@acme.com',
|
|
99
|
+
site: 'https://shop.example/api/auth/pidgeon/token',
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## What it checks before it signs anything
|
|
104
|
+
|
|
105
|
+
- The token request is signed by a key your identity provider gives an
|
|
106
|
+
agent, covers what it must, and is used once (one replica, or pass a
|
|
107
|
+
shared `replay` store).
|
|
108
|
+
- The card is one this issuer signed, current, that agent's, naming that key.
|
|
109
|
+
- The agent is live in the identity provider **now**: disable it there and
|
|
110
|
+
its next sign-in fails.
|
|
111
|
+
- A grant is one this issuer signed, that agent's, still held through its
|
|
112
|
+
group, and for that site.
|
|
113
|
+
|
|
114
|
+
## Left out
|
|
115
|
+
|
|
116
|
+
The person's sign-in, userinfo, introspection and revocation endpoints (an ID
|
|
117
|
+
token lasts five minutes; revoking an agent is disabling it), dynamic client
|
|
118
|
+
registration, and Pidgeon's card log, which would mean sending us your agents.
|
|
119
|
+
Authorization stays yours: a grant is a relation (`customer:42#refunds.issue`
|
|
120
|
+
for an agent), so it can be written from the engine you already run.
|
|
121
|
+
|
|
122
|
+
Apache-2.0.
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
UsageError,
|
|
4
|
+
runIssuer
|
|
5
|
+
} from "./chunk-CCOFMK5K.js";
|
|
6
|
+
|
|
7
|
+
// src/bin.ts
|
|
8
|
+
async function main() {
|
|
9
|
+
try {
|
|
10
|
+
process.stdout.write(
|
|
11
|
+
`${await runIssuer(process.argv.slice(2), process.env, {
|
|
12
|
+
log: (line) => process.stderr.write(`${line}
|
|
13
|
+
`)
|
|
14
|
+
})}
|
|
15
|
+
`
|
|
16
|
+
);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Something went wrong."}
|
|
19
|
+
`);
|
|
20
|
+
process.exit(error instanceof UsageError ? 2 : 1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
void main();
|
|
24
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts"],"sourcesContent":["import { runIssuer, UsageError } from './cli';\n\n/** `pidgeon-issuer <command>`. Nothing else in this package runs anything on import. */\nasync function main() {\n try {\n process.stdout.write(\n `${await runIssuer(process.argv.slice(2), process.env, {\n log: (line) => process.stderr.write(`${line}\\n`),\n })}\\n`,\n );\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : 'Something went wrong.'}\\n`);\n process.exit(error instanceof UsageError ? 2 : 1);\n }\n}\n\nvoid main();\n"],"mappings":";;;;;;;AAGA,eAAe,OAAO;AACpB,MAAI;AACF,YAAQ,OAAO;AAAA,MACb,GAAG,MAAM,UAAU,QAAQ,KAAK,MAAM,CAAC,GAAG,QAAQ,KAAK;AAAA,QACrD,KAAK,CAAC,SAAS,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAAA,MACjD,CAAC,CAAC;AAAA;AAAA,IACJ;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,uBAAuB;AAAA,CAAI;AAC5F,YAAQ,KAAK,iBAAiB,aAAa,IAAI,CAAC;AAAA,EAClD;AACF;AAEA,KAAK,KAAK;","names":[]}
|