@hawcx/oauth-client 0.1.0 โ†’ 4.0.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 (71) hide show
  1. package/README.md +475 -204
  2. package/dist/delegation/credentials.d.ts +92 -0
  3. package/dist/delegation/credentials.d.ts.map +1 -0
  4. package/dist/delegation/credentials.js +215 -0
  5. package/dist/delegation/credentials.js.map +1 -0
  6. package/dist/delegation/crypto.d.ts +2 -2
  7. package/dist/delegation/crypto.d.ts.map +1 -1
  8. package/dist/delegation/crypto.js +41 -32
  9. package/dist/delegation/crypto.js.map +1 -1
  10. package/dist/delegation/exceptions.d.ts +12 -5
  11. package/dist/delegation/exceptions.d.ts.map +1 -1
  12. package/dist/delegation/exceptions.js +12 -14
  13. package/dist/delegation/exceptions.js.map +1 -1
  14. package/dist/delegation/factory.d.ts +20 -0
  15. package/dist/delegation/factory.d.ts.map +1 -0
  16. package/dist/delegation/factory.js +49 -0
  17. package/dist/delegation/factory.js.map +1 -0
  18. package/dist/delegation/hawcx.d.ts +67 -224
  19. package/dist/delegation/hawcx.d.ts.map +1 -1
  20. package/dist/delegation/hawcx.js +116 -299
  21. package/dist/delegation/hawcx.js.map +1 -1
  22. package/dist/delegation/index.d.ts +4 -9
  23. package/dist/delegation/index.d.ts.map +1 -1
  24. package/dist/delegation/index.js +5 -17
  25. package/dist/delegation/index.js.map +1 -1
  26. package/dist/delegation/transport.d.ts +40 -0
  27. package/dist/delegation/transport.d.ts.map +1 -0
  28. package/dist/delegation/transport.js +120 -0
  29. package/dist/delegation/transport.js.map +1 -0
  30. package/dist/errors.d.ts +14 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +29 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/index.d.ts +10 -15
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +15 -27
  37. package/dist/index.js.map +1 -1
  38. package/dist/oauth.d.ts +98 -0
  39. package/dist/oauth.d.ts.map +1 -0
  40. package/dist/oauth.js +150 -0
  41. package/dist/oauth.js.map +1 -0
  42. package/dist/stepup/client.d.ts +26 -0
  43. package/dist/stepup/client.d.ts.map +1 -0
  44. package/dist/stepup/client.js +84 -0
  45. package/dist/stepup/client.js.map +1 -0
  46. package/dist/stepup/index.d.ts +3 -0
  47. package/dist/stepup/index.d.ts.map +1 -0
  48. package/dist/stepup/index.js +19 -0
  49. package/dist/stepup/index.js.map +1 -0
  50. package/dist/stepup/types.d.ts +36 -0
  51. package/dist/stepup/types.d.ts.map +1 -0
  52. package/dist/stepup/types.js +3 -0
  53. package/dist/stepup/types.js.map +1 -0
  54. package/package.json +16 -17
  55. package/LICENSE +0 -22
  56. package/dist/client.d.ts +0 -134
  57. package/dist/client.d.ts.map +0 -1
  58. package/dist/client.js +0 -301
  59. package/dist/client.js.map +0 -1
  60. package/dist/delegation/client.d.ts +0 -116
  61. package/dist/delegation/client.d.ts.map +0 -1
  62. package/dist/delegation/client.js +0 -227
  63. package/dist/delegation/client.js.map +0 -1
  64. package/dist/exceptions.d.ts +0 -52
  65. package/dist/exceptions.d.ts.map +0 -1
  66. package/dist/exceptions.js +0 -81
  67. package/dist/exceptions.js.map +0 -1
  68. package/dist/utils.d.ts +0 -61
  69. package/dist/utils.d.ts.map +0 -1
  70. package/dist/utils.js +0 -263
  71. package/dist/utils.js.map +0 -1
package/README.md CHANGED
@@ -1,304 +1,575 @@
1
- # Hawcx OAuth Client SDK (Node.js)
1
+ # @hawcx/oauth-client
2
2
 
3
- A lightweight, production-ready Node.js/TypeScript library for exchanging OAuth authorization codes and verifying JWT claims. Built with security, reliability, and ease of use in mind.
3
+ Simple, production-ready OAuth client SDK for Hawcx authentication.
4
4
 
5
5
  ## Features
6
6
 
7
- - ๐Ÿ” **Secure JWT Verification**: RS256/ES256/EdDSA signature verification with configurable validation
8
- - ๐Ÿ†• **PKCE Support**: Native PKCE (RFC 7636) support for enhanced OAuth security
9
- - ๐Ÿš€ **Simple API**: One function call to go from code to verified claims
10
- - ๐ŸŽฏ **High-Level Hawcx Client**: One-line MFA setup with automatic encryption/signing
11
- - ๐Ÿ›ก๏ธ **Comprehensive Error Handling**: Clear, actionable error messages for all failure scenarios
12
- - ๐Ÿ“ **TypeScript First**: Full type definitions for better IDE support and type checking
13
- - ๐Ÿงช **Production Ready**: Extensive error handling and security best practices
14
- - ๐Ÿ”ง **Express/Fastify Compatible**: Works with any Node.js framework
15
- - ๐Ÿ” **Detailed Logging**: Structured logging for debugging (no sensitive data logged)
7
+ - **Simple JWT Verification** - Uses `jose` library for automatic JWKS caching and signature handling
8
+ - **PKCE Support** - Native PKCE (RFC 7636) support for enhanced OAuth security
9
+ - **Delegation API** - Server-to-server user management (MFA, devices)
10
+ - **Step-Up Auth** - Step-up authentication flows for sensitive operations
11
+ - **TypeScript First** - Full type definitions included
12
+ - **Zero Config Crypto** - All cryptographic complexity hidden behind a single secret key
16
13
 
17
14
  ## Installation
18
15
 
19
16
  ```bash
20
17
  npm install @hawcx/oauth-client
18
+ ```
19
+
20
+ ## Quick Start
21
21
 
22
- # or with yarn
23
- yarn add @hawcx/oauth-client
22
+ ```typescript
23
+ import { HawcxOAuth } from '@hawcx/oauth-client';
24
+
25
+ const oauth = new HawcxOAuth({ configId: 'your-config-id' });
24
26
 
25
- # or with pnpm
26
- pnpm add @hawcx/oauth-client
27
+ // Exchange authorization code for tokens
28
+ const { idToken, claims } = await oauth.exchangeCode(code, codeVerifier);
29
+ console.log(claims.sub); // user ID
30
+ console.log(claims.email); // user email
27
31
  ```
28
32
 
29
- ## Quick Start
33
+ ---
34
+
35
+ ## Modules
36
+
37
+ | Module | Purpose |
38
+ |--------|---------|
39
+ | **OAuth** | Token exchange and JWT verification |
40
+ | **Delegation** | Server-to-server user management (MFA, devices) |
41
+ | **Step-Up** | Step-up authentication flows |
42
+
43
+ ---
44
+
45
+ ## OAuth Module
46
+
47
+ Core module for OAuth 2.1 + PKCE authentication.
30
48
 
31
- ### OAuth Code Exchange
49
+ ### `HawcxOAuth`
32
50
 
33
51
  ```typescript
34
- import { exchangeCodeForClaims } from '@hawcx/oauth-client';
35
-
36
- // Exchange authorization code for verified claims
37
- const claims = await exchangeCodeForClaims({
38
- code: req.body.code,
39
- oauthTokenUrl: process.env.OAUTH_TOKEN_ENDPOINT!,
40
- clientId: process.env.OAUTH_CLIENT_ID!,
41
- publicKey: process.env.OAUTH_PUBLIC_KEY!,
42
- // Optional (recommended for production):
43
- codeVerifier: session.pkceVerifier, // PKCE support
44
- audience: 'my-app', // Validate 'aud' claim
45
- issuer: 'https://oauth.example.com', // Validate 'iss' claim
46
- leeway: 10 // Clock skew tolerance
52
+ import { HawcxOAuth } from '@hawcx/oauth-client';
53
+
54
+ const oauth = new HawcxOAuth({
55
+ configId: 'your-config-id', // Required: Your Hawcx config ID
56
+ baseUrl: 'https://api.hawcx.com', // Optional: API base URL
57
+ timeout: 10000, // Optional: Request timeout in ms
47
58
  });
59
+ ```
60
+
61
+ ### Methods
62
+
63
+ #### `exchangeCode(code, codeVerifier): Promise<ExchangeResult>`
64
+
65
+ Exchange an authorization code for tokens.
66
+
67
+ ```typescript
68
+ const { idToken, claims } = await oauth.exchangeCode(
69
+ 'authorization-code',
70
+ 'pkce-code-verifier'
71
+ );
72
+ ```
73
+
74
+ **Returns:**
75
+ ```typescript
76
+ interface ExchangeResult {
77
+ idToken: string; // Raw JWT token
78
+ claims: JwtClaims; // Verified claims
79
+ }
80
+ ```
81
+
82
+ #### `verifyToken(token): Promise<JwtClaims>`
83
+
84
+ Verify an existing JWT token.
85
+
86
+ ```typescript
87
+ const claims = await oauth.verifyToken(idToken);
88
+ ```
89
+
90
+ **Returns:**
91
+ ```typescript
92
+ interface JwtClaims {
93
+ sub?: string; // User ID
94
+ iss?: string; // Issuer
95
+ aud?: string | string[]; // Audience
96
+ iat?: number; // Issued at (unix timestamp)
97
+ exp?: number; // Expiration (unix timestamp)
98
+ email?: string; // User email
99
+ name?: string; // User name
100
+ amr?: string[]; // Authentication methods
101
+ mfa_method?: string; // MFA method used
102
+ }
103
+ ```
104
+
105
+ #### `clearCache(): void`
106
+
107
+ Clear the JWKS cache (useful for testing or key rotation).
108
+
109
+ ```typescript
110
+ oauth.clearCache();
111
+ ```
112
+
113
+ ### Errors
114
+
115
+ ```typescript
116
+ import {
117
+ HawcxOAuthError, // Base error class
118
+ TokenExchangeError, // Code exchange failed
119
+ TokenVerificationError // JWT verification failed
120
+ } from '@hawcx/oauth-client';
48
121
 
49
- // Use the verified claims
50
- const userId = claims.sub;
51
- // Mint your own access token (SDK only verifies, doesn't mint)
122
+ try {
123
+ await oauth.exchangeCode(code, verifier);
124
+ } catch (error) {
125
+ if (error instanceof TokenExchangeError) {
126
+ console.log(error.statusCode); // HTTP status code
127
+ }
128
+ if (error instanceof TokenVerificationError) {
129
+ console.log(error.message); // "Token expired", "Invalid signature", etc.
130
+ }
131
+ }
52
132
  ```
53
133
 
54
- ### Hawcx Delegation (MFA Setup)
134
+ ---
135
+
136
+ ## Delegation Module
55
137
 
56
- For Hawcx MFA setup and user management, use the delegation client:
138
+ Server-to-server API for managing users, MFA, and devices. Requires a Hawcx secret key.
139
+
140
+ ### `DelegationClient`
57
141
 
58
142
  ```typescript
59
- import { HawcxDelegationClient, MfaMethod } from '@hawcx/oauth-client';
60
-
61
- // ๐ŸŽ‰ One-line initialization from explicit keys!
62
- const client = HawcxDelegationClient.fromKeys({
63
- spSigningKey: process.env.SP_ED25519_PRIVATE_KEY_PEM!,
64
- spEncryptionKey: process.env.SP_X25519_PRIVATE_KEY_PEM!,
65
- idpVerifyKey: process.env.IDP_ED25519_PUBLIC_KEY_PEM!,
66
- idpEncryptionKey: process.env.IDP_X25519_PUBLIC_KEY_PEM!,
67
- baseUrl: 'https://ceasar-api.hawcx.com',
68
- spId: process.env.OAUTH_CLIENT_ID!
143
+ import { DelegationClient, MfaMethod } from '@hawcx/oauth-client';
144
+
145
+ const client = DelegationClient.fromSecretKey({
146
+ baseUrl: 'https://api.hawcx.com',
147
+ secretKey: process.env.HAWCX_SECRET_KEY!,
148
+ apiKey: 'optional-api-key', // Optional
149
+ timeoutSeconds: 15, // Optional (default: 15)
150
+ clockSkewSeconds: 300, // Optional (default: 300)
69
151
  });
152
+ ```
153
+
154
+ ### MFA Operations
155
+
156
+ #### `client.mfa.initiate(options): Promise<object>`
157
+
158
+ Initiate MFA setup or change for a user.
70
159
 
71
- // Initiate MFA setup (Email, SMS, or TOTP)
72
- const result = await client.initiateMfaChange({
73
- userid: "user@example.com",
74
- mfaMethod: MfaMethod.SMS,
75
- phoneNumber: "+15551234567"
160
+ ```typescript
161
+ // Email MFA
162
+ const result = await client.mfa.initiate({
163
+ userId: 'user@example.com',
164
+ mfaMethod: MfaMethod.EMAIL,
165
+ });
166
+
167
+ // SMS MFA (requires phone number)
168
+ const result = await client.mfa.initiate({
169
+ userId: 'user@example.com',
170
+ mfaMethod: MfaMethod.SMS,
171
+ phoneNumber: '+1234567890',
76
172
  });
77
173
 
78
- // Verify OTP and complete MFA setup
79
- await client.verifyMfaChange({
80
- userid: "user@example.com",
81
- sessionId: result.session_id,
82
- otp: "123456"
174
+ // TOTP MFA
175
+ const result = await client.mfa.initiate({
176
+ userId: 'user@example.com',
177
+ mfaMethod: MfaMethod.TOTP,
83
178
  });
84
179
 
85
- // Get user credentials
86
- const creds = await client.getUserCredentials("user@example.com");
87
- console.log(`MFA method: ${creds.mfa_method}`);
180
+ // Change MFA method
181
+ const result = await client.mfa.initiate({
182
+ userId: 'user@example.com',
183
+ mfaMethod: MfaMethod.EMAIL, // Current method for verification
184
+ mfaChangeTo: MfaMethod.TOTP, // New method to switch to
185
+ });
88
186
  ```
89
187
 
90
- **What it does automatically**: ECIES encryption, Ed25519 signatures, request/response crypto, Hawcx payload formatting, type-safe `MfaMethod` enum
188
+ **MFA Methods:**
189
+ ```typescript
190
+ enum MfaMethod {
191
+ EMAIL = 'email',
192
+ SMS = 'sms',
193
+ TOTP = 'totp',
194
+ }
195
+ ```
91
196
 
92
- ## Demo App
197
+ #### `client.mfa.verify(options): Promise<object>`
93
198
 
94
- A simple demo application is included to show the OAuth and delegation flows in action:
199
+ Verify MFA setup with OTP.
95
200
 
96
- ```bash
97
- cd demo
98
- cp env.template .env
99
- # Edit .env with your credentials
100
- npm install
101
- npm run dev
102
- # Visit http://localhost:3000
201
+ ```typescript
202
+ const result = await client.mfa.verify({
203
+ userId: 'user@example.com',
204
+ sessionId: 'session-from-initiate',
205
+ otp: '123456', // 6-digit code
206
+ });
103
207
  ```
104
208
 
105
- See [demo/README.md](demo/README.md) for details.
209
+ ### User Operations
106
210
 
107
- ## Configuration
211
+ #### `client.users.getCredentials(userId): Promise<object>`
108
212
 
109
- ### Environment Variables
213
+ Get user credentials/metadata.
110
214
 
111
- **For OAuth Code Exchange:**
112
- ```bash
113
- OAUTH_TOKEN_ENDPOINT="https://oauth.example.com/token"
114
- OAUTH_CLIENT_ID="your-client-id"
115
- OAUTH_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."
116
- OAUTH_ISSUER="https://oauth.example.com" # Optional but recommended
117
- OAUTH_AUDIENCE="your-client-id" # Optional but recommended
215
+ ```typescript
216
+ const creds = await client.users.getCredentials('user@example.com');
217
+ console.log(creds.mfa_method);
118
218
  ```
119
219
 
120
- **For Hawcx Delegation:**
121
- ```bash
122
- SP_ED25519_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
123
- SP_X25519_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
124
- IDP_ED25519_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----..."
125
- IDP_X25519_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----..."
220
+ ### Device Operations
221
+
222
+ #### `client.devices.list(userId): Promise<object>`
223
+
224
+ List all devices for a user.
225
+
226
+ ```typescript
227
+ const { devices } = await client.devices.list('user@example.com');
126
228
  ```
127
229
 
128
- ### Public Key Formats
230
+ #### `client.devices.revoke(options): Promise<object>`
129
231
 
130
- The SDK accepts public keys in multiple formats:
232
+ Revoke a device (block it from authenticating).
131
233
 
132
- **From file:**
133
234
  ```typescript
134
- const claims = await exchangeCodeForClaims({
135
- // ...
136
- publicKey: '/path/to/public.pem' // Absolute path
235
+ await client.devices.revoke({
236
+ userId: 'user@example.com',
237
+ deviceId: 'device-h2index',
137
238
  });
138
239
  ```
139
240
 
140
- **From environment variable:**
241
+ #### `client.devices.unrevoke(options): Promise<object>`
242
+
243
+ Unrevoke a previously revoked device.
244
+
141
245
  ```typescript
142
- const claims = await exchangeCodeForClaims({
143
- // ...
144
- publicKey: process.env.OAUTH_PUBLIC_KEY! // PEM string
246
+ await client.devices.unrevoke({
247
+ userId: 'user@example.com',
248
+ deviceId: 'device-h2index',
145
249
  });
146
250
  ```
147
251
 
148
- **Expected PEM format:**
252
+ #### `client.devices.delete(options): Promise<object>`
253
+
254
+ Permanently delete a device.
255
+
256
+ ```typescript
257
+ await client.devices.delete({
258
+ userId: 'user@example.com',
259
+ deviceId: 'device-h2index',
260
+ });
261
+ ```
262
+
263
+ ### Generic Request
264
+
265
+ #### `client.request<TReq, TRes>(options): Promise<TRes>`
266
+
267
+ Send a custom encrypted request to any delegation endpoint.
268
+
269
+ ```typescript
270
+ const result = await client.request({
271
+ endpoint: '/hc_auth/v5/custom/endpoint',
272
+ payload: { userid: 'user@example.com', custom_field: 'value' },
273
+ headers: { 'X-Custom-Header': 'value' },
274
+ includeBaseUrl: true, // Optional (default: true)
275
+ });
149
276
  ```
150
- -----BEGIN PUBLIC KEY-----
151
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
152
- -----END PUBLIC KEY-----
277
+
278
+ ### Credential Utilities
279
+
280
+ #### `parseHawcxSecretKey(secretKey): ParsedCredentials`
281
+
282
+ Parse a secret key into usable key material (advanced usage).
283
+
284
+ ```typescript
285
+ import { parseHawcxSecretKey } from '@hawcx/oauth-client';
286
+
287
+ const creds = parseHawcxSecretKey(process.env.HAWCX_SECRET_KEY!);
288
+ // Returns: { kid, hkid, signingKeyPem, verifyKeyPem, encryptKeyPem, decryptKeyPem }
153
289
  ```
154
290
 
155
- ## Error Handling
291
+ #### `generateCredentialBlob(options): string`
156
292
 
157
- The SDK provides specific exceptions for different failure scenarios:
293
+ Generate a new credential blob (for admin tooling).
294
+
295
+ ```typescript
296
+ import { generateCredentialBlob } from '@hawcx/oauth-client';
297
+
298
+ const secretKey = generateCredentialBlob({
299
+ kid: 'customer-key-id',
300
+ hkid: 'hawcx-key-id',
301
+ edPrivate: Buffer.alloc(32), // Ed25519 private key (32 bytes)
302
+ xPrivate: Buffer.alloc(32), // X25519 private key (32 bytes)
303
+ hawcxEdPublic: Buffer.alloc(32), // Hawcx Ed25519 public key (32 bytes)
304
+ hawcxXPublic: Buffer.alloc(32), // Hawcx X25519 public key (32 bytes)
305
+ });
306
+ // Returns: "hwx_sk_v1_<base64url>"
307
+ ```
308
+
309
+ ### Delegation Errors
158
310
 
159
311
  ```typescript
160
312
  import {
161
- exchangeCodeForClaims,
162
- OAuthExchangeError,
163
- JWTVerificationError,
164
- InvalidPublicKeyError
313
+ DelegationError, // Base delegation error
314
+ DelegationCryptoError, // Encryption/signing failed
315
+ DelegationRequestError, // Request to IDP failed
316
+ DelegationResponseError, // Invalid response from IDP
165
317
  } from '@hawcx/oauth-client';
166
318
 
167
319
  try {
168
- const claims = await exchangeCodeForClaims({
169
- code,
170
- oauthTokenUrl,
171
- clientId,
172
- publicKey
173
- });
320
+ await client.mfa.initiate({ userId: 'user@example.com', mfaMethod: MfaMethod.EMAIL });
174
321
  } catch (error) {
175
- if (error instanceof OAuthExchangeError) {
176
- // Code exchange failed (invalid code, network error, etc.)
177
- console.error(`Exchange failed: ${error.message}`);
178
- console.error(`HTTP Status: ${error.statusCode}`);
179
- console.error(`Response: ${error.responseBody}`);
180
- } else if (error instanceof JWTVerificationError) {
181
- // JWT verification failed (invalid signature, expired, etc.)
182
- console.error(`Verification failed: ${error.message}`);
183
- console.error(`Original error: ${error.originalError}`);
184
- } else if (error instanceof InvalidPublicKeyError) {
185
- // Public key is invalid or unreadable
186
- console.error(`Key error: ${error.message}`);
322
+ if (error instanceof DelegationRequestError) {
323
+ console.log(error.statusCode); // HTTP status
324
+ console.log(error.responseBody); // Response body
187
325
  }
188
326
  }
189
327
  ```
190
328
 
191
- ### Common Error Scenarios
329
+ ---
192
330
 
193
- | Exception | Common Causes | Recommended Action |
194
- |-----------|--------------|-------------------|
195
- | `OAuthExchangeError` | Invalid/expired code, network issues | Ask user to re-authenticate |
196
- | `JWTVerificationError` | Token tampering, expired token | Log incident, ask user to re-authenticate |
197
- | `InvalidPublicKeyError` | Wrong key, file not found | Check configuration, verify key format |
331
+ ## Step-Up Module
198
332
 
199
- ## Security Best Practices
333
+ For step-up authentication flows (e.g., changing MFA method requires re-authentication).
200
334
 
201
- ### โœ… DO
335
+ ### `StepUpClient`
202
336
 
203
- - **Always validate audience and issuer** in production environments
204
- - **Use HTTPS** for all OAuth endpoints
205
- - **Store keys securely** (environment variables, secrets manager)
206
- - **Set appropriate leeway** for clock skew (5-10 seconds typical)
207
- - **Log authentication failures** for security monitoring
208
- - **Rotate keys regularly** following your security policy
337
+ ```typescript
338
+ import { StepUpClient } from '@hawcx/oauth-client';
339
+
340
+ const stepUp = StepUpClient.fromSecretKey({
341
+ baseUrl: 'https://api.hawcx.com',
342
+ secretKey: process.env.HAWCX_SECRET_KEY!,
343
+ relyingParty: 'your-app.com', // Required
344
+ apiKey: 'optional-api-key', // Optional
345
+ apiPrefix: '/v1', // Optional (default: '/v1')
346
+ timeoutSeconds: 15, // Optional
347
+ clockSkewSeconds: 300, // Optional
348
+ });
349
+ ```
350
+
351
+ ### Methods
352
+
353
+ #### `stepUp.startToken(options): Promise<StepUpStartTokenResponse>`
209
354
 
210
- ### โŒ DON'T
355
+ Start a step-up authentication flow.
211
356
 
212
- - **Never log JWT tokens** or claims containing sensitive data
213
- - **Don't disable signature verification** in production
214
- - **Don't use the id_token as your application's access token** (mint your own)
215
- - **Don't commit keys** to version control
216
- - **Don't ignore verification errors** or catch them silently
357
+ ```typescript
358
+ const { start_token, expires_in } = await stepUp.startToken({
359
+ userId: 'user@example.com',
360
+ purpose: 'change_mfa_method',
361
+ newMfaMethod: 'totp', // 'email_otp' | 'sms_otp' | 'totp'
362
+ });
363
+ // Send start_token to client for step-up authentication
364
+ ```
217
365
 
218
- ## API Reference
366
+ #### `stepUp.consumeReceipt(options): Promise<StepUpConsumeResponse>`
219
367
 
220
- ### `exchangeCodeForClaims()`
368
+ Consume a step-up receipt after user completes authentication.
221
369
 
222
370
  ```typescript
223
- async function exchangeCodeForClaims(
224
- options: ExchangeCodeOptions
225
- ): Promise<Record<string, any>>
226
- ```
227
-
228
- **Parameters:**
229
- - `code` (string): Authorization code from OAuth flow
230
- - `oauthTokenUrl` (string): Token endpoint URL
231
- - `clientId` (string): OAuth client identifier
232
- - `publicKey` (string): RS256 public key (PEM string or file path)
233
- - `codeVerifier` (string, optional): PKCE code verifier (RFC 7636)
234
- - `redirectUri` (string, optional): OAuth redirect URI
235
- - `timeout` (number, optional): Request timeout in seconds (default: 20)
236
- - `audience` (string, optional): Expected 'aud' claim (recommended)
237
- - `issuer` (string, optional): Expected 'iss' claim (recommended)
238
- - `verifyExp` (boolean, optional): Verify token expiration (default: true)
239
- - `leeway` (number, optional): Clock skew tolerance in seconds (default: 0)
240
- - `algorithms` (string[], optional): Allowed signing algorithms (default: RS256, ES256/384/512, EdDSA)
241
-
242
- **Returns:** `Promise<Record<string, any>>` - Verified JWT claims
243
-
244
- **Throws:**
245
- - `OAuthExchangeError`: Code exchange failed
246
- - `JWTVerificationError`: JWT verification failed
247
- - `InvalidPublicKeyError`: Public key invalid
248
-
249
- ### `verifyJwt()`
371
+ const { ok } = await stepUp.consumeReceipt({
372
+ receipt: 'step-up-receipt-from-client',
373
+ });
374
+
375
+ if (ok) {
376
+ // Step-up verified, proceed with sensitive operation
377
+ }
378
+ ```
379
+
380
+ ### Types
250
381
 
251
382
  ```typescript
252
- function verifyJwt(
253
- options: VerifyJwtOptions
254
- ): Record<string, any>
383
+ type StepUpPurpose = 'change_mfa_method';
384
+
385
+ type HxAuthMfaMethod = 'sms_otp' | 'email_otp' | 'totp';
386
+
387
+ interface StepUpStartTokenResponse {
388
+ start_token: string;
389
+ expires_in: number;
390
+ }
391
+
392
+ interface StepUpConsumeResponse {
393
+ ok: boolean;
394
+ }
255
395
  ```
256
396
 
257
- Verify a JWT and return its claims. See full documentation in [API.md](./API.md).
397
+ ---
258
398
 
259
- ### HawcxDelegationClient
399
+ ## Examples
260
400
 
261
- See [DELEGATION.md](./DELEGATION.md) for full Hawcx delegation client documentation.
401
+ ### Next.js Auth Callback
262
402
 
263
- ## TypeScript Support
403
+ ```typescript
404
+ // app/api/auth/callback/route.ts
405
+ import { HawcxOAuth, TokenExchangeError } from '@hawcx/oauth-client';
406
+ import { cookies } from 'next/headers';
407
+
408
+ const oauth = new HawcxOAuth({
409
+ configId: process.env.HAWCX_CONFIG_ID!,
410
+ });
264
411
 
265
- This package is written in TypeScript and includes full type definitions:
412
+ export async function GET(request: Request) {
413
+ const url = new URL(request.url);
414
+ const code = url.searchParams.get('code');
415
+
416
+ const cookieStore = cookies();
417
+ const codeVerifier = cookieStore.get('code_verifier')?.value;
418
+
419
+ if (!code || !codeVerifier) {
420
+ return Response.redirect('/login?error=missing_params');
421
+ }
422
+
423
+ try {
424
+ const { idToken, claims } = await oauth.exchangeCode(code, codeVerifier);
425
+ // Create session with claims.sub, claims.email, etc.
426
+ return Response.redirect('/dashboard');
427
+ } catch (error) {
428
+ if (error instanceof TokenExchangeError) {
429
+ console.error('Token exchange failed:', error.message);
430
+ }
431
+ return Response.redirect('/login?error=auth_failed');
432
+ }
433
+ }
434
+ ```
435
+
436
+ ### Admin MFA Management
266
437
 
267
438
  ```typescript
268
- import type {
269
- ExchangeCodeOptions,
270
- VerifyJwtOptions
271
- } from '@hawcx/oauth-client';
439
+ import { DelegationClient, MfaMethod } from '@hawcx/oauth-client';
440
+
441
+ const admin = DelegationClient.fromSecretKey({
442
+ baseUrl: process.env.HAWCX_API_URL!,
443
+ secretKey: process.env.HAWCX_SECRET_KEY!,
444
+ });
445
+
446
+ // Reset user MFA to email
447
+ async function resetUserMfa(userId: string, otp: string) {
448
+ const initResult = await admin.mfa.initiate({
449
+ userId,
450
+ mfaMethod: MfaMethod.EMAIL,
451
+ });
272
452
 
273
- // All options are fully typed
274
- const options: ExchangeCodeOptions = {
275
- code: 'abc123',
276
- oauthTokenUrl: 'https://oauth.example.com/token',
277
- clientId: 'my-app',
278
- publicKey: process.env.OAUTH_PUBLIC_KEY!,
279
- audience: 'my-app', // TypeScript will catch typos!
280
- };
453
+ const verifyResult = await admin.mfa.verify({
454
+ userId,
455
+ sessionId: initResult.session_id,
456
+ otp,
457
+ });
458
+
459
+ return verifyResult;
460
+ }
461
+
462
+ // Revoke all user devices
463
+ async function revokeAllDevices(userId: string) {
464
+ const { devices } = await admin.devices.list(userId);
465
+
466
+ for (const device of devices) {
467
+ await admin.devices.revoke({
468
+ userId,
469
+ deviceId: device.h2index,
470
+ });
471
+ }
472
+ }
281
473
  ```
282
474
 
283
- ## Development
475
+ ### Step-Up for MFA Change
284
476
 
285
- ### Building
477
+ ```typescript
478
+ import { StepUpClient, DelegationClient, MfaMethod } from '@hawcx/oauth-client';
286
479
 
287
- ```bash
288
- npm install
289
- npm run build
480
+ const stepUp = StepUpClient.fromSecretKey({
481
+ baseUrl: process.env.HAWCX_API_URL!,
482
+ secretKey: process.env.HAWCX_SECRET_KEY!,
483
+ relyingParty: 'myapp.com',
484
+ });
485
+
486
+ const delegation = DelegationClient.fromSecretKey({
487
+ baseUrl: process.env.HAWCX_API_URL!,
488
+ secretKey: process.env.HAWCX_SECRET_KEY!,
489
+ });
490
+
491
+ async function changeMfaMethod(userId: string, newMethod: 'totp' | 'sms_otp' | 'email_otp') {
492
+ // 1. Start step-up flow
493
+ const { start_token } = await stepUp.startToken({
494
+ userId,
495
+ purpose: 'change_mfa_method',
496
+ newMfaMethod: newMethod,
497
+ });
498
+
499
+ // 2. Send start_token to client, user completes step-up auth
500
+ // 3. Client sends back receipt
501
+
502
+ // 4. Verify step-up receipt
503
+ const { ok } = await stepUp.consumeReceipt({ receipt: 'receipt-from-client' });
504
+
505
+ if (!ok) {
506
+ throw new Error('Step-up verification failed');
507
+ }
508
+
509
+ // 5. Now safe to change MFA
510
+ await delegation.mfa.initiate({
511
+ userId,
512
+ mfaMethod: MfaMethod.EMAIL,
513
+ mfaChangeTo: newMethod,
514
+ });
515
+ }
290
516
  ```
291
517
 
292
- ### Testing
518
+ ---
293
519
 
294
- ```bash
295
- npm test
296
- npm run test:coverage
520
+ ## Environment Variables
521
+
522
+ | Variable | Description |
523
+ |----------|-------------|
524
+ | `HAWCX_CONFIG_ID` | Your Hawcx OAuth config ID (for token verification) |
525
+ | `HAWCX_SECRET_KEY` | Your Hawcx secret key (for delegation/step-up APIs) |
526
+ | `HAWCX_API_URL` | API base URL (default: `https://api.hawcx.com`) |
527
+
528
+ ### Secret Key Format
529
+
530
+ The `HAWCX_SECRET_KEY` is a compact credential blob:
531
+
532
+ ```
533
+ hwx_sk_v1_<base64url-encoded-json>
297
534
  ```
298
535
 
299
- ### Linting
536
+ It contains:
537
+ - Your Ed25519 signing key (for request authentication)
538
+ - Your X25519 decryption key (for response decryption)
539
+ - Hawcx's Ed25519 public key (for response verification)
540
+ - Hawcx's X25519 public key (for request encryption)
541
+ - Key IDs for rotation tracking
300
542
 
301
- ```bash
302
- npm run lint
303
- npm run format
543
+ Generate via the Hawcx Dashboard or admin tools.
544
+
545
+ ---
546
+
547
+ ## JWKS Caching
548
+
549
+ The SDK automatically caches JWKS (JSON Web Key Set) for JWT verification:
550
+
551
+ - Keys are fetched lazily on first `verifyToken()` call
552
+ - Cached in memory for the lifetime of the `HawcxOAuth` instance
553
+ - Auto-refreshes if a key ID isn't found (handles key rotation)
554
+ - Call `clearCache()` to force a refresh
555
+
556
+ ```typescript
557
+ // Keys fetched once, cached thereafter
558
+ const claims1 = await oauth.verifyToken(token1); // Fetches JWKS
559
+ const claims2 = await oauth.verifyToken(token2); // Uses cache
560
+ const claims3 = await oauth.verifyToken(token3); // Uses cache
561
+
562
+ // Force refresh if needed
563
+ oauth.clearCache();
564
+ const claims4 = await oauth.verifyToken(token4); // Fetches JWKS again
304
565
  ```
566
+
567
+ ---
568
+
569
+ ## Requirements
570
+
571
+ - Node.js >= 18.0.0
572
+
573
+ ## License
574
+
575
+ MIT