@flarcos/kiota-authentication-gnap 0.1.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 +192 -0
- package/dist/contentDigest.d.ts +17 -0
- package/dist/contentDigest.d.ts.map +1 -0
- package/dist/contentDigest.js +23 -0
- package/dist/contentDigest.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/gnapAccessTokenProvider.d.ts +50 -0
- package/dist/gnapAccessTokenProvider.d.ts.map +1 -0
- package/dist/gnapAccessTokenProvider.js +176 -0
- package/dist/gnapAccessTokenProvider.js.map +1 -0
- package/dist/gnapAuthenticationProvider.d.ts +68 -0
- package/dist/gnapAuthenticationProvider.d.ts.map +1 -0
- package/dist/gnapAuthenticationProvider.js +216 -0
- package/dist/gnapAuthenticationProvider.js.map +1 -0
- package/dist/gnapTokenStore.d.ts +11 -0
- package/dist/gnapTokenStore.d.ts.map +1 -0
- package/dist/gnapTokenStore.js +27 -0
- package/dist/gnapTokenStore.js.map +1 -0
- package/dist/httpMessageSigner.d.ts +41 -0
- package/dist/httpMessageSigner.d.ts.map +1 -0
- package/dist/httpMessageSigner.js +74 -0
- package/dist/httpMessageSigner.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/keyManagement.d.ts +36 -0
- package/dist/keyManagement.d.ts.map +1 -0
- package/dist/keyManagement.js +75 -0
- package/dist/keyManagement.js.map +1 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @flarcos/kiota-authentication-gnap
|
|
2
|
+
|
|
3
|
+
GNAP ([RFC 9635](https://www.rfc-editor.org/rfc/rfc9635.html)) authentication provider for [Microsoft Kiota](https://github.com/microsoft/kiota)-generated API clients.
|
|
4
|
+
|
|
5
|
+
Enables Kiota-generated SDKs to authenticate with APIs that use the Grant Negotiation and Authorization Protocol (GNAP), such as [Open Payments](https://openpayments.dev/).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ **Kiota `AuthenticationProvider` interface** — drop-in integration
|
|
10
|
+
- ✅ **RFC 9635 GNAP** — full grant lifecycle (request, continue, rotate, revoke)
|
|
11
|
+
- ✅ **RFC 9421 HTTP Message Signatures** — Ed25519 request signing
|
|
12
|
+
- ✅ **RFC 9530 Content-Digest** — SHA-256 body integrity
|
|
13
|
+
- ✅ **RFC 7638 JWK Thumbprint** — automatic key ID derivation
|
|
14
|
+
- ✅ **Token caching** — in-memory store with TTL expiration
|
|
15
|
+
- ✅ **Interactive grants** — pluggable `InteractionHandler` for redirect flows
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @flarcos/kiota-authentication-gnap @microsoft/kiota-abstractions
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { GnapAuthenticationProvider } from "@flarcos/kiota-authentication-gnap";
|
|
27
|
+
import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary";
|
|
28
|
+
import fs from "node:fs";
|
|
29
|
+
|
|
30
|
+
// Create the GNAP auth provider
|
|
31
|
+
const authProvider = new GnapAuthenticationProvider({
|
|
32
|
+
authServerUrl: "https://auth.wallet.example/.gnap",
|
|
33
|
+
privateKey: fs.readFileSync("private-key.pem", "utf-8"),
|
|
34
|
+
clientIdentifier: "https://wallet.example/alice",
|
|
35
|
+
accessRights: [
|
|
36
|
+
{ type: "incoming-payment", actions: ["create", "read"] },
|
|
37
|
+
{ type: "outgoing-payment", actions: ["create", "read"] },
|
|
38
|
+
{ type: "quote", actions: ["create", "read"] },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Use with a Kiota-generated client
|
|
43
|
+
const adapter = new FetchRequestAdapter(authProvider);
|
|
44
|
+
// const client = new OpenPaymentsClient(adapter);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage with Open Payments
|
|
48
|
+
|
|
49
|
+
Open Payments uses GNAP for authorization. Here's how to create a payment:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { GnapAuthenticationProvider } from "@flarcos/kiota-authentication-gnap";
|
|
53
|
+
|
|
54
|
+
const authProvider = new GnapAuthenticationProvider({
|
|
55
|
+
// The receiving wallet's auth server
|
|
56
|
+
authServerUrl: "https://auth.interledger-test.dev/",
|
|
57
|
+
// Your Ed25519 private key (PEM format)
|
|
58
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
59
|
+
// Your wallet address URL
|
|
60
|
+
clientIdentifier: "https://ilp.interledger-test.dev/alice",
|
|
61
|
+
// Request access to create incoming payments
|
|
62
|
+
accessRights: [
|
|
63
|
+
{
|
|
64
|
+
type: "incoming-payment",
|
|
65
|
+
actions: ["create", "read", "complete"],
|
|
66
|
+
identifier: "https://ilp.interledger-test.dev/bob",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Interactive Grants (with user consent)
|
|
73
|
+
|
|
74
|
+
For operations that require user interaction (e.g., outgoing payments), provide an `InteractionHandler`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import type { InteractionHandler, InteractionResult } from "@flarcos/kiota-authentication-gnap";
|
|
78
|
+
|
|
79
|
+
class MyInteractionHandler implements InteractionHandler {
|
|
80
|
+
async handleRedirect(redirectUrl: string, nonce: string): Promise<InteractionResult> {
|
|
81
|
+
// Open browser for user to approve
|
|
82
|
+
console.log(`Please visit: ${redirectUrl}`);
|
|
83
|
+
|
|
84
|
+
// Wait for callback and return the interact_ref
|
|
85
|
+
return {
|
|
86
|
+
interactRef: "received-interact-ref",
|
|
87
|
+
hash: "received-hash",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const authProvider = new GnapAuthenticationProvider({
|
|
93
|
+
authServerUrl: "https://auth.interledger-test.dev/",
|
|
94
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
95
|
+
clientIdentifier: "https://ilp.interledger-test.dev/alice",
|
|
96
|
+
accessRights: [
|
|
97
|
+
{
|
|
98
|
+
type: "outgoing-payment",
|
|
99
|
+
actions: ["create", "read"],
|
|
100
|
+
identifier: "https://ilp.interledger-test.dev/alice",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
interact: {
|
|
104
|
+
start: ["redirect"],
|
|
105
|
+
finish: {
|
|
106
|
+
method: "redirect",
|
|
107
|
+
uri: "http://localhost:3000/callback",
|
|
108
|
+
nonce: crypto.randomUUID(),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
interactionHandler: new MyInteractionHandler(),
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## API Reference
|
|
116
|
+
|
|
117
|
+
### `GnapAuthenticationProvider`
|
|
118
|
+
|
|
119
|
+
Main class implementing Kiota's `AuthenticationProvider`.
|
|
120
|
+
|
|
121
|
+
#### Constructor Options
|
|
122
|
+
|
|
123
|
+
| Option | Type | Required | Description |
|
|
124
|
+
|--------|------|----------|-------------|
|
|
125
|
+
| `authServerUrl` | `string` | ✅ | Authorization Server grant endpoint |
|
|
126
|
+
| `privateKey` | `string` | ✅ | Ed25519 private key (PEM format) |
|
|
127
|
+
| `clientIdentifier` | `string` | ✅ | Client identifier (wallet address URL) |
|
|
128
|
+
| `accessRights` | `GnapAccessRight[]` | ✅ | Access rights to request |
|
|
129
|
+
| `interact` | `object` | ❌ | Interaction mode configuration |
|
|
130
|
+
| `interactionHandler` | `InteractionHandler` | ❌ | Handler for interactive grants |
|
|
131
|
+
| `tokenStore` | `GnapTokenStore` | ❌ | Token cache (defaults to in-memory) |
|
|
132
|
+
| `allowedHosts` | `string[]` | ❌ | Restrict auth to specific hosts |
|
|
133
|
+
|
|
134
|
+
#### Methods
|
|
135
|
+
|
|
136
|
+
- `authenticateRequest(request)` — Authenticate a Kiota request (called automatically)
|
|
137
|
+
- `rotateToken()` — Manually rotate the current access token
|
|
138
|
+
- `revokeToken()` — Revoke the current access token
|
|
139
|
+
|
|
140
|
+
### Standalone Utilities
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import {
|
|
144
|
+
createHttpSignature, // RFC 9421 signing
|
|
145
|
+
computeContentDigest, // RFC 9530 body hash
|
|
146
|
+
computeJwkThumbprint, // RFC 7638 key ID
|
|
147
|
+
loadPrivateKey, // PEM → KeyObject
|
|
148
|
+
} from "@flarcos/kiota-authentication-gnap";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## How It Works
|
|
152
|
+
|
|
153
|
+
When `authenticateRequest` is called, the provider:
|
|
154
|
+
|
|
155
|
+
1. **Checks the token cache** for a valid, non-expired GNAP access token
|
|
156
|
+
2. **Negotiates a new grant** if no cached token exists (POST to AS)
|
|
157
|
+
3. **Handles interaction** if the AS requires user consent (via `InteractionHandler`)
|
|
158
|
+
4. **Sets the `Authorization: GNAP <token>` header** on the request
|
|
159
|
+
5. **Computes `Content-Digest`** (SHA-256) for requests with a body
|
|
160
|
+
6. **Signs the request** with RFC 9421 HTTP Message Signatures (Ed25519)
|
|
161
|
+
|
|
162
|
+
## Standards Compliance
|
|
163
|
+
|
|
164
|
+
| Standard | Description | Coverage |
|
|
165
|
+
|----------|-------------|----------|
|
|
166
|
+
| [RFC 9635](https://www.rfc-editor.org/rfc/rfc9635.html) | GNAP Core Protocol | §2 Grant Request, §3 Response, §5 Continuation, §6 Token Management |
|
|
167
|
+
| [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html) | HTTP Message Signatures | §2 Signature Base, §3 Creating Signatures (Ed25519) |
|
|
168
|
+
| [RFC 9530](https://www.rfc-editor.org/rfc/rfc9530.html) | Content-Digest | SHA-256 digest |
|
|
169
|
+
| [RFC 7638](https://www.rfc-editor.org/rfc/rfc7638.html) | JWK Thumbprint | SHA-256 thumbprint for key identification |
|
|
170
|
+
|
|
171
|
+
## Project Structure
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
src/
|
|
175
|
+
├── index.ts # Public exports
|
|
176
|
+
├── gnapAuthenticationProvider.ts # Kiota AuthenticationProvider implementation
|
|
177
|
+
├── gnapAccessTokenProvider.ts # GNAP grant lifecycle
|
|
178
|
+
├── gnapTokenStore.ts # In-memory token cache
|
|
179
|
+
├── httpMessageSigner.ts # RFC 9421 HTTP Message Signatures
|
|
180
|
+
├── contentDigest.ts # RFC 9530 Content-Digest
|
|
181
|
+
├── keyManagement.ts # Ed25519 key utilities
|
|
182
|
+
├── types.ts # Type definitions
|
|
183
|
+
└── errors.ts # Error classes
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
Apache-2.0
|
|
189
|
+
|
|
190
|
+
## Acknowledgements
|
|
191
|
+
|
|
192
|
+
This package was created as part of the [Interledger Foundation Open Payments SDK Grant Program](https://interledger.org/grant/open-payments-sdk).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9530 Content-Digest computation.
|
|
3
|
+
* @see https://www.rfc-editor.org/rfc/rfc9530.html
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compute the Content-Digest header value for an HTTP request body.
|
|
7
|
+
* Uses SHA-256 as required by Open Payments.
|
|
8
|
+
*
|
|
9
|
+
* @param body - The request body as a Buffer or Uint8Array
|
|
10
|
+
* @returns The Content-Digest header value (e.g. "sha-256=:base64value:")
|
|
11
|
+
*/
|
|
12
|
+
export declare function computeContentDigest(body: Uint8Array): string;
|
|
13
|
+
/**
|
|
14
|
+
* Compute Content-Digest from a string body (UTF-8 encoded).
|
|
15
|
+
*/
|
|
16
|
+
export declare function computeContentDigestFromString(body: string): string;
|
|
17
|
+
//# sourceMappingURL=contentDigest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentDigest.d.ts","sourceRoot":"","sources":["../src/contentDigest.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAG7D;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9530 Content-Digest computation.
|
|
3
|
+
* @see https://www.rfc-editor.org/rfc/rfc9530.html
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
/**
|
|
7
|
+
* Compute the Content-Digest header value for an HTTP request body.
|
|
8
|
+
* Uses SHA-256 as required by Open Payments.
|
|
9
|
+
*
|
|
10
|
+
* @param body - The request body as a Buffer or Uint8Array
|
|
11
|
+
* @returns The Content-Digest header value (e.g. "sha-256=:base64value:")
|
|
12
|
+
*/
|
|
13
|
+
export function computeContentDigest(body) {
|
|
14
|
+
const hash = createHash("sha-256").update(body).digest("base64");
|
|
15
|
+
return `sha-256=:${hash}:`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Compute Content-Digest from a string body (UTF-8 encoded).
|
|
19
|
+
*/
|
|
20
|
+
export function computeContentDigestFromString(body) {
|
|
21
|
+
return computeContentDigest(Buffer.from(body, "utf-8"));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=contentDigest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentDigest.js","sourceRoot":"","sources":["../src/contentDigest.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAgB;IACnD,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjE,OAAO,YAAY,IAAI,GAAG,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,8BAA8B,CAAC,IAAY;IACzD,OAAO,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AAC1D,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GNAP-specific error classes.
|
|
3
|
+
*/
|
|
4
|
+
export declare class GnapError extends Error {
|
|
5
|
+
readonly code?: string | undefined;
|
|
6
|
+
readonly statusCode?: number | undefined;
|
|
7
|
+
constructor(message: string, code?: string | undefined, statusCode?: number | undefined);
|
|
8
|
+
}
|
|
9
|
+
export declare class GnapGrantError extends GnapError {
|
|
10
|
+
constructor(code: string, description?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class GnapInteractionRequiredError extends GnapError {
|
|
13
|
+
readonly redirectUrl: string;
|
|
14
|
+
readonly continueUri: string;
|
|
15
|
+
readonly continueAccessToken: string;
|
|
16
|
+
readonly finishNonce?: string | undefined;
|
|
17
|
+
constructor(redirectUrl: string, continueUri: string, continueAccessToken: string, finishNonce?: string | undefined);
|
|
18
|
+
}
|
|
19
|
+
export declare class GnapTokenExpiredError extends GnapError {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
22
|
+
export declare class GnapSignatureError extends GnapError {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qBAAa,SAAU,SAAQ,KAAK;aAGhB,IAAI,CAAC,EAAE,MAAM;aACb,UAAU,CAAC,EAAE,MAAM;gBAFnC,OAAO,EAAE,MAAM,EACC,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,UAAU,CAAC,EAAE,MAAM,YAAA;CAKtC;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;CAI/C;AAED,qBAAa,4BAA6B,SAAQ,SAAS;aAEvC,WAAW,EAAE,MAAM;aACnB,WAAW,EAAE,MAAM;aACnB,mBAAmB,EAAE,MAAM;aAC3B,WAAW,CAAC,EAAE,MAAM;gBAHpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,EAC3B,WAAW,CAAC,EAAE,MAAM,YAAA;CAKvC;AAED,qBAAa,qBAAsB,SAAQ,SAAS;;CAKnD;AAED,qBAAa,kBAAmB,SAAQ,SAAS;gBACnC,OAAO,EAAE,MAAM;CAI5B"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GNAP-specific error classes.
|
|
3
|
+
*/
|
|
4
|
+
export class GnapError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
statusCode;
|
|
7
|
+
constructor(message, code, statusCode) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.statusCode = statusCode;
|
|
11
|
+
this.name = "GnapError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class GnapGrantError extends GnapError {
|
|
15
|
+
constructor(code, description) {
|
|
16
|
+
super(description ?? `GNAP grant error: ${code}`, code);
|
|
17
|
+
this.name = "GnapGrantError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class GnapInteractionRequiredError extends GnapError {
|
|
21
|
+
redirectUrl;
|
|
22
|
+
continueUri;
|
|
23
|
+
continueAccessToken;
|
|
24
|
+
finishNonce;
|
|
25
|
+
constructor(redirectUrl, continueUri, continueAccessToken, finishNonce) {
|
|
26
|
+
super("GNAP grant requires user interaction");
|
|
27
|
+
this.redirectUrl = redirectUrl;
|
|
28
|
+
this.continueUri = continueUri;
|
|
29
|
+
this.continueAccessToken = continueAccessToken;
|
|
30
|
+
this.finishNonce = finishNonce;
|
|
31
|
+
this.name = "GnapInteractionRequiredError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class GnapTokenExpiredError extends GnapError {
|
|
35
|
+
constructor() {
|
|
36
|
+
super("GNAP access token has expired");
|
|
37
|
+
this.name = "GnapTokenExpiredError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class GnapSignatureError extends GnapError {
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = "GnapSignatureError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAGhB;IACA;IAHlB,YACE,OAAe,EACC,IAAa,EACb,UAAmB;QAEnC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAS;QACb,eAAU,GAAV,UAAU,CAAS;QAGnC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,SAAS;IAC3C,YAAY,IAAY,EAAE,WAAoB;QAC5C,KAAK,CAAC,WAAW,IAAI,qBAAqB,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,4BAA6B,SAAQ,SAAS;IAEvC;IACA;IACA;IACA;IAJlB,YACkB,WAAmB,EACnB,WAAmB,EACnB,mBAA2B,EAC3B,WAAoB;QAEpC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAL9B,gBAAW,GAAX,WAAW,CAAQ;QACnB,gBAAW,GAAX,WAAW,CAAQ;QACnB,wBAAmB,GAAnB,mBAAmB,CAAQ;QAC3B,gBAAW,GAAX,WAAW,CAAS;QAGpC,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,SAAS;IAClD;QACE,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,SAAS;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GNAP Access Token Provider.
|
|
3
|
+
* Handles the full GNAP (RFC 9635) grant lifecycle:
|
|
4
|
+
* - Grant request (§2)
|
|
5
|
+
* - Grant continuation (§5)
|
|
6
|
+
* - Token rotation (§6.1)
|
|
7
|
+
* - Token revocation (§6.2)
|
|
8
|
+
*
|
|
9
|
+
* All requests to the Authorization Server are signed with RFC 9421.
|
|
10
|
+
*/
|
|
11
|
+
import type { KeyObject } from "node:crypto";
|
|
12
|
+
import type { GnapGrantRequest, GnapAccessToken, GnapAccessRight } from "./types.js";
|
|
13
|
+
export interface GrantRequestParams {
|
|
14
|
+
/** Authorization Server grant endpoint URL */
|
|
15
|
+
authServerUrl: string;
|
|
16
|
+
/** Client identifier (wallet address URL) */
|
|
17
|
+
clientIdentifier: string;
|
|
18
|
+
/** Client public key in JWK format */
|
|
19
|
+
clientKeyJwk: JsonWebKey;
|
|
20
|
+
/** Client private key for signing */
|
|
21
|
+
privateKey: KeyObject;
|
|
22
|
+
/** JWK thumbprint key ID */
|
|
23
|
+
keyId: string;
|
|
24
|
+
/** Access rights to request */
|
|
25
|
+
accessRights: GnapAccessRight[];
|
|
26
|
+
/** Interaction configuration */
|
|
27
|
+
interact?: GnapGrantRequest["interact"];
|
|
28
|
+
}
|
|
29
|
+
export declare class GnapAccessTokenProvider {
|
|
30
|
+
/**
|
|
31
|
+
* Request a new grant from the Authorization Server.
|
|
32
|
+
* Returns the access token for non-interactive grants,
|
|
33
|
+
* or throws GnapInteractionRequiredError if user consent is needed.
|
|
34
|
+
*/
|
|
35
|
+
requestGrant(params: GrantRequestParams): Promise<GnapAccessToken>;
|
|
36
|
+
/**
|
|
37
|
+
* Continue a grant after user interaction (§5.1).
|
|
38
|
+
* Called with the interact_ref obtained from the interaction callback.
|
|
39
|
+
*/
|
|
40
|
+
continueGrant(continueUri: string, continueAccessToken: string, interactRef: string, privateKey: KeyObject, keyId: string): Promise<GnapAccessToken>;
|
|
41
|
+
/**
|
|
42
|
+
* Rotate an access token (§6.1).
|
|
43
|
+
*/
|
|
44
|
+
rotateToken(manageUrl: string, existingToken: string, privateKey: KeyObject, keyId: string): Promise<GnapAccessToken>;
|
|
45
|
+
/**
|
|
46
|
+
* Revoke an access token (§6.2).
|
|
47
|
+
*/
|
|
48
|
+
revokeToken(manageUrl: string, existingToken: string, privateKey: KeyObject, keyId: string): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=gnapAccessTokenProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gnapAccessTokenProvider.d.ts","sourceRoot":"","sources":["../src/gnapAccessTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,OAAO,KAAK,EACV,gBAAgB,EAEhB,eAAe,EACf,eAAe,EAEhB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,YAAY,EAAE,UAAU,CAAC;IACzB,qCAAqC;IACrC,UAAU,EAAE,SAAS,CAAC;IACtB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;CACzC;AAED,qBAAa,uBAAuB;IAClC;;;;OAIG;IACG,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC;IA4FxE;;;OAGG;IACG,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,mBAAmB,EAAE,MAAM,EAC3B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,SAAS,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,CAAC;IAmD3B;;OAEG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,SAAS,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,CAAC;IAkC3B;;OAEG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,SAAS,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;CA6BjB"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GNAP Access Token Provider.
|
|
3
|
+
* Handles the full GNAP (RFC 9635) grant lifecycle:
|
|
4
|
+
* - Grant request (§2)
|
|
5
|
+
* - Grant continuation (§5)
|
|
6
|
+
* - Token rotation (§6.1)
|
|
7
|
+
* - Token revocation (§6.2)
|
|
8
|
+
*
|
|
9
|
+
* All requests to the Authorization Server are signed with RFC 9421.
|
|
10
|
+
*/
|
|
11
|
+
import { createHttpSignature } from "./httpMessageSigner.js";
|
|
12
|
+
import { computeContentDigestFromString } from "./contentDigest.js";
|
|
13
|
+
import { GnapGrantError, GnapInteractionRequiredError } from "./errors.js";
|
|
14
|
+
export class GnapAccessTokenProvider {
|
|
15
|
+
/**
|
|
16
|
+
* Request a new grant from the Authorization Server.
|
|
17
|
+
* Returns the access token for non-interactive grants,
|
|
18
|
+
* or throws GnapInteractionRequiredError if user consent is needed.
|
|
19
|
+
*/
|
|
20
|
+
async requestGrant(params) {
|
|
21
|
+
const { authServerUrl, clientIdentifier, clientKeyJwk, privateKey, keyId, accessRights, interact, } = params;
|
|
22
|
+
// Build the GNAP grant request body (§2)
|
|
23
|
+
// Open Payments uses a simplified client format:
|
|
24
|
+
// - string: wallet address URL (backwards compatible)
|
|
25
|
+
// - { walletAddress: "..." }: wallet address in object form
|
|
26
|
+
// - { jwk: {...} }: directed identity (non-interactive only)
|
|
27
|
+
const grantRequest = {
|
|
28
|
+
access_token: {
|
|
29
|
+
access: accessRights,
|
|
30
|
+
},
|
|
31
|
+
client: clientIdentifier,
|
|
32
|
+
};
|
|
33
|
+
if (interact) {
|
|
34
|
+
grantRequest.interact = interact;
|
|
35
|
+
}
|
|
36
|
+
const body = JSON.stringify(grantRequest);
|
|
37
|
+
// Compute Content-Digest
|
|
38
|
+
const contentDigest = computeContentDigestFromString(body);
|
|
39
|
+
// Build headers
|
|
40
|
+
const headers = new Map();
|
|
41
|
+
headers.set("content-type", "application/json");
|
|
42
|
+
headers.set("content-digest", contentDigest);
|
|
43
|
+
headers.set("content-length", Buffer.byteLength(body).toString());
|
|
44
|
+
// Sign the request (RFC 9421)
|
|
45
|
+
const { signature, signatureInput } = await createHttpSignature({
|
|
46
|
+
privateKey,
|
|
47
|
+
keyId,
|
|
48
|
+
method: "POST",
|
|
49
|
+
targetUri: authServerUrl,
|
|
50
|
+
headers,
|
|
51
|
+
hasBody: true,
|
|
52
|
+
});
|
|
53
|
+
// Send the grant request
|
|
54
|
+
const response = await fetch(authServerUrl, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"Content-Digest": contentDigest,
|
|
59
|
+
"Content-Length": Buffer.byteLength(body).toString(),
|
|
60
|
+
Signature: signature,
|
|
61
|
+
"Signature-Input": signatureInput,
|
|
62
|
+
},
|
|
63
|
+
body,
|
|
64
|
+
});
|
|
65
|
+
const grantResponse = (await response.json());
|
|
66
|
+
// Handle error response
|
|
67
|
+
if (grantResponse.error) {
|
|
68
|
+
throw new GnapGrantError(grantResponse.error.code, grantResponse.error.description);
|
|
69
|
+
}
|
|
70
|
+
// Non-interactive: access token returned directly
|
|
71
|
+
if (grantResponse.access_token) {
|
|
72
|
+
return grantResponse.access_token;
|
|
73
|
+
}
|
|
74
|
+
// Interactive: AS requires user consent
|
|
75
|
+
if (grantResponse.interact?.redirect && grantResponse.continue) {
|
|
76
|
+
throw new GnapInteractionRequiredError(grantResponse.interact.redirect, grantResponse.continue.uri, grantResponse.continue.access_token.value, grantResponse.interact.finish);
|
|
77
|
+
}
|
|
78
|
+
throw new GnapGrantError("invalid_grant_response", `Unexpected grant response (HTTP ${response.status}): no access_token or interact field`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Continue a grant after user interaction (§5.1).
|
|
82
|
+
* Called with the interact_ref obtained from the interaction callback.
|
|
83
|
+
*/
|
|
84
|
+
async continueGrant(continueUri, continueAccessToken, interactRef, privateKey, keyId) {
|
|
85
|
+
const body = JSON.stringify({ interact_ref: interactRef });
|
|
86
|
+
const contentDigest = computeContentDigestFromString(body);
|
|
87
|
+
const headers = new Map();
|
|
88
|
+
headers.set("content-type", "application/json");
|
|
89
|
+
headers.set("content-digest", contentDigest);
|
|
90
|
+
headers.set("content-length", Buffer.byteLength(body).toString());
|
|
91
|
+
headers.set("authorization", `GNAP ${continueAccessToken}`);
|
|
92
|
+
const { signature, signatureInput } = await createHttpSignature({
|
|
93
|
+
privateKey,
|
|
94
|
+
keyId,
|
|
95
|
+
method: "POST",
|
|
96
|
+
targetUri: continueUri,
|
|
97
|
+
headers,
|
|
98
|
+
hasBody: true,
|
|
99
|
+
});
|
|
100
|
+
const response = await fetch(continueUri, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
"Content-Digest": contentDigest,
|
|
105
|
+
"Content-Length": Buffer.byteLength(body).toString(),
|
|
106
|
+
Authorization: `GNAP ${continueAccessToken}`,
|
|
107
|
+
Signature: signature,
|
|
108
|
+
"Signature-Input": signatureInput,
|
|
109
|
+
},
|
|
110
|
+
body,
|
|
111
|
+
});
|
|
112
|
+
const grantResponse = (await response.json());
|
|
113
|
+
if (grantResponse.error) {
|
|
114
|
+
throw new GnapGrantError(grantResponse.error.code, grantResponse.error.description);
|
|
115
|
+
}
|
|
116
|
+
if (grantResponse.access_token) {
|
|
117
|
+
return grantResponse.access_token;
|
|
118
|
+
}
|
|
119
|
+
throw new GnapGrantError("invalid_continue_response", `Continue response did not include access_token (HTTP ${response.status})`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Rotate an access token (§6.1).
|
|
123
|
+
*/
|
|
124
|
+
async rotateToken(manageUrl, existingToken, privateKey, keyId) {
|
|
125
|
+
const headers = new Map();
|
|
126
|
+
headers.set("authorization", `GNAP ${existingToken}`);
|
|
127
|
+
const { signature, signatureInput } = await createHttpSignature({
|
|
128
|
+
privateKey,
|
|
129
|
+
keyId,
|
|
130
|
+
method: "POST",
|
|
131
|
+
targetUri: manageUrl,
|
|
132
|
+
headers,
|
|
133
|
+
hasBody: false,
|
|
134
|
+
});
|
|
135
|
+
const response = await fetch(manageUrl, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `GNAP ${existingToken}`,
|
|
139
|
+
Signature: signature,
|
|
140
|
+
"Signature-Input": signatureInput,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const tokenResponse = (await response.json());
|
|
144
|
+
if (!tokenResponse.value) {
|
|
145
|
+
throw new GnapGrantError("invalid_rotation_response", `Token rotation failed (HTTP ${response.status})`);
|
|
146
|
+
}
|
|
147
|
+
return tokenResponse;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Revoke an access token (§6.2).
|
|
151
|
+
*/
|
|
152
|
+
async revokeToken(manageUrl, existingToken, privateKey, keyId) {
|
|
153
|
+
const headers = new Map();
|
|
154
|
+
headers.set("authorization", `GNAP ${existingToken}`);
|
|
155
|
+
const { signature, signatureInput } = await createHttpSignature({
|
|
156
|
+
privateKey,
|
|
157
|
+
keyId,
|
|
158
|
+
method: "DELETE",
|
|
159
|
+
targetUri: manageUrl,
|
|
160
|
+
headers,
|
|
161
|
+
hasBody: false,
|
|
162
|
+
});
|
|
163
|
+
const response = await fetch(manageUrl, {
|
|
164
|
+
method: "DELETE",
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `GNAP ${existingToken}`,
|
|
167
|
+
Signature: signature,
|
|
168
|
+
"Signature-Input": signatureInput,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new GnapGrantError("revocation_failed", `Token revocation failed (HTTP ${response.status})`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=gnapAccessTokenProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gnapAccessTokenProvider.js","sourceRoot":"","sources":["../src/gnapAccessTokenProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GNAP Authentication Provider for Kiota.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Kiota AuthenticationProvider interface to authenticate
|
|
5
|
+
* requests using the GNAP protocol (RFC 9635) with HTTP Message Signatures
|
|
6
|
+
* (RFC 9421).
|
|
7
|
+
*
|
|
8
|
+
* This is the main entry point for the package. Usage:
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { GnapAuthenticationProvider } from "@flarcos/kiota-authentication-gnap";
|
|
12
|
+
*
|
|
13
|
+
* const authProvider = new GnapAuthenticationProvider({
|
|
14
|
+
* authServerUrl: "https://auth.wallet.example/.well-known/...",
|
|
15
|
+
* privateKey: fs.readFileSync("private-key.pem", "utf-8"),
|
|
16
|
+
* clientIdentifier: "https://wallet.example/alice",
|
|
17
|
+
* accessRights: [{ type: "outgoing-payment", actions: ["create", "read"] }],
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const adapter = new FetchRequestAdapter(authProvider);
|
|
21
|
+
* const client = new OpenPaymentsClient(adapter);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import type { AuthenticationProvider } from "@microsoft/kiota-abstractions";
|
|
25
|
+
import type { RequestInformation } from "@microsoft/kiota-abstractions";
|
|
26
|
+
import type { GnapAuthenticationProviderOptions, GnapAccessToken } from "./types.js";
|
|
27
|
+
export declare class GnapAuthenticationProvider implements AuthenticationProvider {
|
|
28
|
+
private readonly authServerUrl;
|
|
29
|
+
private readonly clientIdentifier;
|
|
30
|
+
private readonly accessRights;
|
|
31
|
+
private readonly interact;
|
|
32
|
+
private readonly interactionHandler?;
|
|
33
|
+
private readonly tokenStore;
|
|
34
|
+
private readonly allowedHosts;
|
|
35
|
+
private readonly tokenProvider;
|
|
36
|
+
private keyPromise;
|
|
37
|
+
private readonly rawKey;
|
|
38
|
+
constructor(options: GnapAuthenticationProviderOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Lazily initialize the key material.
|
|
41
|
+
*/
|
|
42
|
+
private getKeyMaterial;
|
|
43
|
+
/**
|
|
44
|
+
* Authenticate a Kiota request by:
|
|
45
|
+
* 1. Obtaining/caching a GNAP access token
|
|
46
|
+
* 2. Setting the Authorization: GNAP header
|
|
47
|
+
* 3. Computing Content-Digest for request bodies
|
|
48
|
+
* 4. Signing the request with HTTP Message Signatures
|
|
49
|
+
*/
|
|
50
|
+
authenticateRequest: (request: RequestInformation, additionalAuthenticationContext?: Record<string, unknown>) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Get an access token — either from cache or by negotiating a new grant.
|
|
53
|
+
*/
|
|
54
|
+
private getAccessToken;
|
|
55
|
+
/**
|
|
56
|
+
* Handle interactive grants (redirect-based user consent).
|
|
57
|
+
*/
|
|
58
|
+
private handleInteraction;
|
|
59
|
+
/**
|
|
60
|
+
* Rotate the current access token.
|
|
61
|
+
*/
|
|
62
|
+
rotateToken(): Promise<GnapAccessToken>;
|
|
63
|
+
/**
|
|
64
|
+
* Revoke the current access token.
|
|
65
|
+
*/
|
|
66
|
+
revokeToken(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=gnapAuthenticationProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gnapAuthenticationProvider.d.ts","sourceRoot":"","sources":["../src/gnapAuthenticationProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAexE,OAAO,KAAK,EACV,iCAAiC,EAIjC,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,qBAAa,0BAA2B,YAAW,sBAAsB;IACvE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoD;IACjF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;IACzE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IAGxD,OAAO,CAAC,UAAU,CAID;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,OAAO,EAAE,iCAAiC;IAYtD;;OAEG;YACW,cAAc;IAW5B;;;;;;OAMG;IACH,mBAAmB,GACjB,SAAS,kBAAkB,EAC3B,kCAAkC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACxD,OAAO,CAAC,IAAI,CAAC,CA0Dd;IAEF;;OAEG;YACW,cAAc;IAuC5B;;OAEG;YACW,iBAAiB;IAuC/B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;IA8B7C;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;CAmBnC"}
|