@stackwright-pro/auth 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.
@@ -0,0 +1,1009 @@
1
+ import { z } from 'zod';
2
+ import { X509Certificate } from '@peculiar/x509';
3
+ import * as React from 'react';
4
+ import React__default, { ReactNode, ReactElement } from 'react';
5
+
6
+ /**
7
+ * Core Type Definitions for Stackwright Auth
8
+ *
9
+ * These types define the foundational contract for authentication.
10
+ * All auth functionality builds on these interfaces.
11
+ */
12
+ /**
13
+ * Authenticated user representation
14
+ * Normalized across PKI and OIDC providers
15
+ */
16
+ interface AuthUser {
17
+ id: string;
18
+ email?: string;
19
+ name?: string;
20
+ roles: string[];
21
+ permissions?: string[];
22
+ metadata?: Record<string, any>;
23
+ }
24
+ /**
25
+ * Session representation
26
+ * Contains user info and expiration details
27
+ */
28
+ interface AuthSession {
29
+ user: AuthUser;
30
+ expiresAt: number;
31
+ issuedAt: number;
32
+ refreshToken?: string;
33
+ }
34
+ /**
35
+ * PKI-specific configuration
36
+ * Supports DoD CAC, PIV cards, and custom PKI deployments
37
+ */
38
+ interface PKIConfig {
39
+ type: 'pki';
40
+ profile: 'dod_cac' | 'piv' | 'custom';
41
+ source: 'gateway_headers' | 'direct_tls';
42
+ headerPrefix?: string;
43
+ verifiedHeader?: string;
44
+ requiredValue?: string;
45
+ caChain?: string;
46
+ requiredOU?: string[];
47
+ allowedIssuers?: string[];
48
+ }
49
+ /**
50
+ * OIDC configuration
51
+ * Supports major providers + custom OIDC implementations
52
+ */
53
+ interface OIDCConfig {
54
+ type: 'oidc';
55
+ provider: 'cognito' | 'azure_ad' | 'authentik' | 'keycloak' | 'okta' | 'auth0' | 'custom';
56
+ discoveryUrl: string;
57
+ clientId: string;
58
+ clientSecret: string;
59
+ redirectUri?: string;
60
+ claimsMapping?: {
61
+ user_id?: string;
62
+ email?: string;
63
+ name?: string;
64
+ roles?: string;
65
+ };
66
+ quirks?: {
67
+ skipIssuerCheck?: boolean;
68
+ useRefreshTokenRotation?: boolean;
69
+ };
70
+ }
71
+ /**
72
+ * Union of all auth configurations
73
+ * Discriminated by 'type' field for type safety
74
+ */
75
+ type AuthConfig = PKIConfig | OIDCConfig;
76
+ /**
77
+ * Component-level auth configuration (from YAML)
78
+ * Defines access requirements for individual components
79
+ */
80
+ interface ComponentAuthConfig {
81
+ required_roles?: string[];
82
+ required_permissions?: string[];
83
+ fallback?: 'hide' | 'placeholder' | 'message';
84
+ fallback_message?: string;
85
+ }
86
+ /**
87
+ * Role-based access control configuration
88
+ * Defines roles, permissions, and route protection
89
+ */
90
+ interface RBACConfig {
91
+ roles: Array<{
92
+ name: string;
93
+ permissions?: string[];
94
+ }>;
95
+ protected_routes?: Array<{
96
+ path: string;
97
+ roles: string[];
98
+ }>;
99
+ public_routes?: string[];
100
+ }
101
+ /**
102
+ * Auth context passed to providers during authentication
103
+ */
104
+ interface AuthContext$1 {
105
+ headers?: Record<string, string>;
106
+ query?: Record<string, string>;
107
+ cookies?: Record<string, string>;
108
+ }
109
+ /**
110
+ * Provider interface that both PKI and OIDC implementations must satisfy
111
+ * Ensures consistent behavior across auth strategies
112
+ */
113
+ interface AuthProvider$1 {
114
+ authenticate(context: AuthContext$1): Promise<AuthUser | null>;
115
+ validate(session: AuthSession): Promise<boolean>;
116
+ refresh?(session: AuthSession): Promise<AuthSession | null>;
117
+ }
118
+
119
+ /**
120
+ * Zod Schemas for Runtime Validation
121
+ *
122
+ * These schemas provide runtime validation for all auth-related data structures.
123
+ * They mirror the TypeScript types but enforce validation at runtime.
124
+ */
125
+
126
+ /**
127
+ * PKI Configuration Schema
128
+ * Validates PKI auth config with sensible defaults
129
+ */
130
+ declare const pkiConfigSchema: z.ZodObject<{
131
+ type: z.ZodLiteral<"pki">;
132
+ profile: z.ZodEnum<{
133
+ dod_cac: "dod_cac";
134
+ piv: "piv";
135
+ custom: "custom";
136
+ }>;
137
+ source: z.ZodEnum<{
138
+ gateway_headers: "gateway_headers";
139
+ direct_tls: "direct_tls";
140
+ }>;
141
+ headerPrefix: z.ZodDefault<z.ZodOptional<z.ZodString>>;
142
+ verifiedHeader: z.ZodDefault<z.ZodOptional<z.ZodString>>;
143
+ requiredValue: z.ZodDefault<z.ZodOptional<z.ZodString>>;
144
+ caChain: z.ZodOptional<z.ZodString>;
145
+ requiredOU: z.ZodOptional<z.ZodArray<z.ZodString>>;
146
+ allowedIssuers: z.ZodOptional<z.ZodArray<z.ZodString>>;
147
+ }, z.core.$strip>;
148
+ /**
149
+ * OIDC Configuration Schema
150
+ * Validates OIDC provider config including claims mapping and quirks
151
+ */
152
+ declare const oidcConfigSchema: z.ZodObject<{
153
+ type: z.ZodLiteral<"oidc">;
154
+ provider: z.ZodEnum<{
155
+ custom: "custom";
156
+ cognito: "cognito";
157
+ azure_ad: "azure_ad";
158
+ authentik: "authentik";
159
+ keycloak: "keycloak";
160
+ okta: "okta";
161
+ auth0: "auth0";
162
+ }>;
163
+ discoveryUrl: z.ZodString;
164
+ clientId: z.ZodString;
165
+ clientSecret: z.ZodString;
166
+ redirectUri: z.ZodOptional<z.ZodString>;
167
+ claimsMapping: z.ZodOptional<z.ZodObject<{
168
+ user_id: z.ZodOptional<z.ZodString>;
169
+ email: z.ZodOptional<z.ZodString>;
170
+ name: z.ZodOptional<z.ZodString>;
171
+ roles: z.ZodOptional<z.ZodString>;
172
+ }, z.core.$strip>>;
173
+ quirks: z.ZodOptional<z.ZodObject<{
174
+ skipIssuerCheck: z.ZodOptional<z.ZodBoolean>;
175
+ useRefreshTokenRotation: z.ZodOptional<z.ZodBoolean>;
176
+ }, z.core.$strip>>;
177
+ }, z.core.$strip>;
178
+ /**
179
+ * Discriminated Union Schema
180
+ * Auto-discriminates based on 'type' field for type-safe validation
181
+ */
182
+ declare const authConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
183
+ type: z.ZodLiteral<"pki">;
184
+ profile: z.ZodEnum<{
185
+ dod_cac: "dod_cac";
186
+ piv: "piv";
187
+ custom: "custom";
188
+ }>;
189
+ source: z.ZodEnum<{
190
+ gateway_headers: "gateway_headers";
191
+ direct_tls: "direct_tls";
192
+ }>;
193
+ headerPrefix: z.ZodDefault<z.ZodOptional<z.ZodString>>;
194
+ verifiedHeader: z.ZodDefault<z.ZodOptional<z.ZodString>>;
195
+ requiredValue: z.ZodDefault<z.ZodOptional<z.ZodString>>;
196
+ caChain: z.ZodOptional<z.ZodString>;
197
+ requiredOU: z.ZodOptional<z.ZodArray<z.ZodString>>;
198
+ allowedIssuers: z.ZodOptional<z.ZodArray<z.ZodString>>;
199
+ }, z.core.$strip>, z.ZodObject<{
200
+ type: z.ZodLiteral<"oidc">;
201
+ provider: z.ZodEnum<{
202
+ custom: "custom";
203
+ cognito: "cognito";
204
+ azure_ad: "azure_ad";
205
+ authentik: "authentik";
206
+ keycloak: "keycloak";
207
+ okta: "okta";
208
+ auth0: "auth0";
209
+ }>;
210
+ discoveryUrl: z.ZodString;
211
+ clientId: z.ZodString;
212
+ clientSecret: z.ZodString;
213
+ redirectUri: z.ZodOptional<z.ZodString>;
214
+ claimsMapping: z.ZodOptional<z.ZodObject<{
215
+ user_id: z.ZodOptional<z.ZodString>;
216
+ email: z.ZodOptional<z.ZodString>;
217
+ name: z.ZodOptional<z.ZodString>;
218
+ roles: z.ZodOptional<z.ZodString>;
219
+ }, z.core.$strip>>;
220
+ quirks: z.ZodOptional<z.ZodObject<{
221
+ skipIssuerCheck: z.ZodOptional<z.ZodBoolean>;
222
+ useRefreshTokenRotation: z.ZodOptional<z.ZodBoolean>;
223
+ }, z.core.$strip>>;
224
+ }, z.core.$strip>], "type">;
225
+ /**
226
+ * Component Auth Configuration Schema
227
+ * Validates YAML-based component auth requirements
228
+ */
229
+ declare const componentAuthSchema: z.ZodOptional<z.ZodObject<{
230
+ required_roles: z.ZodOptional<z.ZodArray<z.ZodString>>;
231
+ required_permissions: z.ZodOptional<z.ZodArray<z.ZodString>>;
232
+ fallback: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
233
+ hide: "hide";
234
+ placeholder: "placeholder";
235
+ message: "message";
236
+ }>>>;
237
+ fallback_message: z.ZodOptional<z.ZodString>;
238
+ }, z.core.$strip>>;
239
+ /**
240
+ * RBAC Configuration Schema
241
+ * Validates role definitions, permissions, and route protection rules
242
+ */
243
+ declare const rbacConfigSchema: z.ZodObject<{
244
+ roles: z.ZodArray<z.ZodObject<{
245
+ name: z.ZodString;
246
+ permissions: z.ZodOptional<z.ZodArray<z.ZodString>>;
247
+ }, z.core.$strip>>;
248
+ protected_routes: z.ZodOptional<z.ZodArray<z.ZodObject<{
249
+ path: z.ZodString;
250
+ roles: z.ZodArray<z.ZodString>;
251
+ }, z.core.$strip>>>;
252
+ public_routes: z.ZodOptional<z.ZodArray<z.ZodString>>;
253
+ }, z.core.$strip>;
254
+ /**
255
+ * Auth User Schema
256
+ * Validates user objects with required id and roles
257
+ */
258
+ declare const authUserSchema: z.ZodObject<{
259
+ id: z.ZodString;
260
+ email: z.ZodOptional<z.ZodString>;
261
+ name: z.ZodOptional<z.ZodString>;
262
+ roles: z.ZodArray<z.ZodString>;
263
+ permissions: z.ZodOptional<z.ZodArray<z.ZodString>>;
264
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
265
+ }, z.core.$strip>;
266
+ /**
267
+ * Auth Session Schema
268
+ * Validates session structure including expiration timestamps
269
+ */
270
+ declare const authSessionSchema: z.ZodObject<{
271
+ user: z.ZodObject<{
272
+ id: z.ZodString;
273
+ email: z.ZodOptional<z.ZodString>;
274
+ name: z.ZodOptional<z.ZodString>;
275
+ roles: z.ZodArray<z.ZodString>;
276
+ permissions: z.ZodOptional<z.ZodArray<z.ZodString>>;
277
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
278
+ }, z.core.$strip>;
279
+ expiresAt: z.ZodNumber;
280
+ issuedAt: z.ZodNumber;
281
+ refreshToken: z.ZodOptional<z.ZodString>;
282
+ }, z.core.$strip>;
283
+
284
+ /**
285
+ * PKI Authentication Provider
286
+ *
287
+ * Implements certificate-based authentication supporting:
288
+ * - Gateway header extraction (e.g., from NGINX, HAProxy)
289
+ * - Direct TLS termination
290
+ * - DoD CAC profile validation
291
+ * - Custom PKI deployments
292
+ */
293
+
294
+ declare class PKIProvider implements AuthProvider$1 {
295
+ private config;
296
+ constructor(config: PKIConfig);
297
+ authenticate(context: AuthContext$1): Promise<AuthUser | null>;
298
+ validate(session: AuthSession): Promise<boolean>;
299
+ /**
300
+ * Extract roles from certificate based on organizational units
301
+ * Can be customized per deployment via subclassing
302
+ */
303
+ private extractRolesFromCertificate;
304
+ }
305
+
306
+ /**
307
+ * OIDC Authentication Provider
308
+ *
309
+ * Implements the AuthProvider interface for OAuth2/OIDC authentication.
310
+ * Supports major providers (Cognito, Azure AD, Keycloak, etc.) with
311
+ * configurable claims mapping and provider-specific quirks handling.
312
+ */
313
+
314
+ /**
315
+ * OIDC Provider
316
+ *
317
+ * Handles OAuth2/OIDC authentication flows including:
318
+ * - Discovery of OIDC configuration
319
+ * - Authorization code exchange
320
+ * - JWT validation
321
+ * - Session refresh
322
+ * - Provider-specific quirks (Keycloak, Cognito, etc.)
323
+ */
324
+ declare class OIDCProvider implements AuthProvider$1 {
325
+ private config;
326
+ private metadata;
327
+ constructor(config: OIDCConfig);
328
+ /**
329
+ * Initialize provider by discovering OIDC configuration
330
+ *
331
+ * Call this during app startup to pre-fetch OIDC metadata.
332
+ * If not called, metadata will be lazily loaded on first use.
333
+ */
334
+ initialize(): Promise<void>;
335
+ /**
336
+ * Get metadata (lazy load if not initialized)
337
+ *
338
+ * @returns OIDC metadata
339
+ */
340
+ private getMetadata;
341
+ /**
342
+ * Authenticate user by exchanging authorization code for tokens
343
+ *
344
+ * This is called after the user is redirected back from the OIDC provider
345
+ * with an authorization code in the query parameters.
346
+ *
347
+ * @param context - Auth context with query params containing authorization code
348
+ * @returns Authenticated user or null if no code present
349
+ * @throws Error if token exchange or validation fails
350
+ */
351
+ authenticate(context: AuthContext$1): Promise<AuthUser | null>;
352
+ /**
353
+ * Validate session (check if token is still valid)
354
+ *
355
+ * Simple time-based validation. For more security, you could:
356
+ * - Call the userinfo endpoint
357
+ * - Verify token hasn't been revoked
358
+ * - Check against a session store
359
+ *
360
+ * @param session - Session to validate
361
+ * @returns true if session is still valid
362
+ */
363
+ validate(session: AuthSession): Promise<boolean>;
364
+ /**
365
+ * Refresh session using refresh token
366
+ *
367
+ * When the access token expires, use the refresh token to get new tokens
368
+ * without requiring the user to re-authenticate.
369
+ *
370
+ * @param session - Session with refresh token
371
+ * @returns Updated session with new tokens, or null if refresh failed
372
+ */
373
+ refresh(session: AuthSession): Promise<AuthSession | null>;
374
+ /**
375
+ * Get authorization URL for initiating OIDC login
376
+ *
377
+ * Redirect users to this URL to start the authentication flow.
378
+ *
379
+ * @param redirectUri - Where to redirect after authentication
380
+ * @param state - CSRF protection token (recommended)
381
+ * @returns Authorization URL
382
+ */
383
+ getAuthorizationUrl(redirectUri: string, state?: string): Promise<string>;
384
+ /**
385
+ * Extract claim value with fallback
386
+ *
387
+ * Tries custom mapped key first, then falls back to default key.
388
+ *
389
+ * @param claims - JWT claims
390
+ * @param mappedKey - Custom mapped key from config
391
+ * @param defaultKey - Default OIDC key
392
+ * @returns Claim value or undefined
393
+ */
394
+ private getClaimValue;
395
+ /**
396
+ * Extract roles from claims (provider-specific logic)
397
+ *
398
+ * Different OIDC providers store roles in different places:
399
+ * - Standard: 'roles' claim
400
+ * - Keycloak: realm_access.roles
401
+ * - Cognito: cognito:groups
402
+ * - Azure AD: roles claim
403
+ *
404
+ * @param claims - JWT claims
405
+ * @returns Array of role strings
406
+ */
407
+ private extractRoles;
408
+ }
409
+
410
+ /**
411
+ * X.509 Certificate Parser
412
+ *
413
+ * Utilities for parsing and validating X.509 certificates.
414
+ * Supports both PEM/DER formats and gateway header extraction.
415
+ */
416
+
417
+ interface ParsedCertificate {
418
+ subject: {
419
+ commonName?: string;
420
+ email?: string;
421
+ organizationalUnit?: string[];
422
+ organization?: string;
423
+ country?: string;
424
+ };
425
+ issuer: {
426
+ commonName?: string;
427
+ organization?: string;
428
+ };
429
+ serialNumber: string;
430
+ notBefore: Date;
431
+ notAfter: Date;
432
+ isValid: boolean;
433
+ }
434
+ /**
435
+ * Parse X.509 certificate from PEM string or DER buffer
436
+ */
437
+ declare function parseCertificate(pemOrDer: string | ArrayBuffer): ParsedCertificate;
438
+ /**
439
+ * Extract EDIPI from DoD CAC certificate
440
+ * EDIPI is stored in OID 2.16.840.1.101.2.1.11.42
441
+ */
442
+ declare function extractEDIPI(cert: X509Certificate): string | undefined;
443
+ /**
444
+ * Validate certificate against DoD CAC requirements
445
+ */
446
+ declare function validateDoDCAC(parsed: ParsedCertificate): boolean;
447
+ /**
448
+ * Parse certificate from gateway headers (x-client-cert-* pattern)
449
+ */
450
+ declare function parseCertFromHeaders(headers: Record<string, string>, prefix?: string): ParsedCertificate | null;
451
+
452
+ /**
453
+ * OIDC Discovery Client
454
+ *
455
+ * Handles OIDC provider discovery and authorization URL generation.
456
+ * Follows the OpenID Connect Discovery 1.0 specification.
457
+ */
458
+ /**
459
+ * OIDC Provider Metadata (from .well-known/openid-configuration)
460
+ */
461
+ interface OIDCMetadata {
462
+ issuer: string;
463
+ authorization_endpoint: string;
464
+ token_endpoint: string;
465
+ jwks_uri: string;
466
+ userinfo_endpoint?: string;
467
+ end_session_endpoint?: string;
468
+ scopes_supported?: string[];
469
+ response_types_supported?: string[];
470
+ }
471
+ /**
472
+ * Discover OIDC provider configuration
473
+ *
474
+ * Fetches the OIDC discovery document from the well-known endpoint.
475
+ * This is the first step in any OIDC flow.
476
+ *
477
+ * @param discoveryUrl - Full URL to .well-known/openid-configuration
478
+ * @returns OIDC metadata with endpoints and capabilities
479
+ * @throws Error if discovery fails or metadata is invalid
480
+ */
481
+ declare function discoverOIDC(discoveryUrl: string): Promise<OIDCMetadata>;
482
+ /**
483
+ * Generate authorization URL for OIDC login
484
+ *
485
+ * Builds the URL to redirect users to for authentication.
486
+ * Follows OAuth 2.0 authorization code flow.
487
+ *
488
+ * @param metadata - OIDC provider metadata from discovery
489
+ * @param clientId - OAuth client ID
490
+ * @param redirectUri - Where to redirect after authentication
491
+ * @param state - CSRF protection token (recommended)
492
+ * @param scopes - OAuth scopes to request
493
+ * @returns Authorization URL to redirect user to
494
+ */
495
+ declare function buildAuthorizationUrl(metadata: OIDCMetadata, clientId: string, redirectUri: string, state?: string, scopes?: string[]): string;
496
+
497
+ /**
498
+ * OIDC Token Exchange & Validation
499
+ *
500
+ * Handles OAuth2 token exchange, refresh, and JWT validation.
501
+ * Uses jose for secure JWT operations.
502
+ */
503
+
504
+ interface TokenSet {
505
+ access_token: string;
506
+ id_token: string;
507
+ refresh_token?: string;
508
+ expires_in: number;
509
+ token_type: string;
510
+ }
511
+ /**
512
+ * Exchange authorization code for tokens
513
+ *
514
+ * This is step 2 of the OAuth2 authorization code flow.
515
+ * The authorization code is exchanged for access/ID/refresh tokens.
516
+ *
517
+ * @param metadata - OIDC provider metadata
518
+ * @param code - Authorization code from callback
519
+ * @param clientId - OAuth client ID
520
+ * @param clientSecret - OAuth client secret
521
+ * @param redirectUri - Must match the redirect_uri used in authorization
522
+ * @returns Token set with access_token, id_token, and optionally refresh_token
523
+ * @throws Error if token exchange fails
524
+ */
525
+ declare function exchangeCodeForTokens(metadata: OIDCMetadata, code: string, clientId: string, clientSecret: string, redirectUri: string): Promise<TokenSet>;
526
+ /**
527
+ * Refresh access token using refresh token
528
+ *
529
+ * When access tokens expire, use the refresh token to get new ones
530
+ * without requiring user to re-authenticate.
531
+ *
532
+ * @param metadata - OIDC provider metadata
533
+ * @param refreshToken - Refresh token from initial token exchange
534
+ * @param clientId - OAuth client ID
535
+ * @param clientSecret - OAuth client secret
536
+ * @returns New token set
537
+ * @throws Error if refresh fails
538
+ */
539
+ declare function refreshAccessToken(metadata: OIDCMetadata, refreshToken: string, clientId: string, clientSecret: string): Promise<TokenSet>;
540
+ /**
541
+ * Validate ID token JWT and extract claims
542
+ *
543
+ * Verifies the JWT signature using the provider's JWKS and validates
544
+ * standard claims (issuer, audience, expiration).
545
+ *
546
+ * This is critical for security - never trust an ID token without validation!
547
+ *
548
+ * @param idToken - JWT ID token from token exchange
549
+ * @param jwksUri - JWKS URI from OIDC metadata
550
+ * @param issuer - Expected issuer (should match token's iss claim)
551
+ * @param clientId - Expected audience (should match token's aud claim)
552
+ * @param skipIssuerCheck - Skip issuer validation (use for Keycloak quirks)
553
+ * @returns Validated JWT payload with user claims
554
+ * @throws Error if JWT validation fails
555
+ */
556
+ declare function validateIdToken(idToken: string, jwksUri: string, issuer: string, clientId: string, skipIssuerCheck?: boolean): Promise<Record<string, any>>;
557
+
558
+ /**
559
+ * Keycloak-specific OIDC Adapter
560
+ *
561
+ * Keycloak has several quirks in its OIDC implementation that require
562
+ * special handling. This adapter normalizes Keycloak behavior.
563
+ *
564
+ * Known Issues:
565
+ * 1. Issuer in token doesn't always match discovery URL
566
+ * 2. Refresh token rotation is inconsistent
567
+ * 3. Claims structure differs from standard OIDC
568
+ * 4. Role mapping is non-standard (realm_access.roles)
569
+ */
570
+ /**
571
+ * Keycloak-specific OIDC adapter
572
+ *
573
+ * Use this when working with Keycloak to handle its many quirks.
574
+ * Tested with Keycloak 19.x - 23.x
575
+ */
576
+ declare class KeycloakAdapter {
577
+ /**
578
+ * Normalize Keycloak issuer (remove /auth prefix if present)
579
+ *
580
+ * Keycloak's issuer URLs changed between versions:
581
+ * - Pre-17: https://keycloak.example.com/auth/realms/myrealm
582
+ * - Post-17: https://keycloak.example.com/realms/myrealm
583
+ *
584
+ * This normalizes to the post-17 format.
585
+ *
586
+ * @param issuer - Issuer from token or metadata
587
+ * @returns Normalized issuer
588
+ */
589
+ static normalizeIssuer(issuer: string): string;
590
+ /**
591
+ * Map Keycloak-specific claims to standard format
592
+ *
593
+ * Keycloak stores roles and groups in non-standard locations:
594
+ * - Roles: realm_access.roles (not standard)
595
+ * - Groups: groups array (sometimes, if configured)
596
+ * - Username: preferred_username (not always 'name')
597
+ *
598
+ * @param tokenPayload - Raw JWT payload from Keycloak
599
+ * @returns Normalized claims matching AuthUser interface
600
+ */
601
+ static mapClaims(tokenPayload: Record<string, any>): Record<string, any>;
602
+ /**
603
+ * Keycloak refresh tokens should be refreshed earlier than spec suggests
604
+ *
605
+ * Keycloak's refresh token rotation is buggy and sometimes fails if you
606
+ * wait too long. Refresh aggressively when less than 10 minutes remain.
607
+ *
608
+ * @param expiresIn - Seconds until token expires
609
+ * @returns true if token should be refreshed now
610
+ */
611
+ static shouldRefreshToken(expiresIn: number): boolean;
612
+ }
613
+
614
+ interface SessionManagerConfig {
615
+ /**
616
+ * Secret key for JWT signing (must be at least 32 bytes)
617
+ */
618
+ secret: string;
619
+ /**
620
+ * Session duration in seconds (default: 15 minutes)
621
+ */
622
+ sessionDuration?: number;
623
+ /**
624
+ * Algorithm for JWT signing (default: HS256)
625
+ */
626
+ algorithm?: string;
627
+ /**
628
+ * Issuer claim for JWT
629
+ */
630
+ issuer?: string;
631
+ /**
632
+ * Audience claim for JWT
633
+ */
634
+ audience?: string;
635
+ }
636
+ declare class SessionManager {
637
+ private secret;
638
+ private sessionDuration;
639
+ private algorithm;
640
+ private issuer?;
641
+ private audience?;
642
+ constructor(config: SessionManagerConfig);
643
+ /**
644
+ * Create a new session for authenticated user
645
+ */
646
+ createSession(user: AuthUser, refreshToken?: string): Promise<AuthSession>;
647
+ /**
648
+ * Sign session into a JWT
649
+ */
650
+ signSession(session: AuthSession): Promise<string>;
651
+ /**
652
+ * Verify and decode session JWT
653
+ */
654
+ verifySession(jwt: string): Promise<AuthSession | null>;
655
+ /**
656
+ * Check if session is expired
657
+ */
658
+ isExpired(session: AuthSession): boolean;
659
+ /**
660
+ * Check if session should be refreshed (within 5 minutes of expiry)
661
+ */
662
+ shouldRefresh(session: AuthSession): boolean;
663
+ /**
664
+ * Refresh session (extend expiration)
665
+ */
666
+ refreshSession(session: AuthSession): Promise<AuthSession>;
667
+ /**
668
+ * Serialize session to string (for cookies/localStorage)
669
+ */
670
+ serialize(session: AuthSession): Promise<string>;
671
+ /**
672
+ * Deserialize session from string
673
+ */
674
+ deserialize(serialized: string): Promise<AuthSession | null>;
675
+ }
676
+
677
+ /**
678
+ * Cookie options for session cookies
679
+ */
680
+ interface CookieOptions {
681
+ /**
682
+ * Cookie name (default: 'stackwright_session')
683
+ */
684
+ name?: string;
685
+ /**
686
+ * Domain for cookie
687
+ */
688
+ domain?: string;
689
+ /**
690
+ * Path for cookie (default: '/')
691
+ */
692
+ path?: string;
693
+ /**
694
+ * Max age in seconds
695
+ */
696
+ maxAge?: number;
697
+ /**
698
+ * HttpOnly flag (default: true)
699
+ */
700
+ httpOnly?: boolean;
701
+ /**
702
+ * Secure flag (default: true in production)
703
+ */
704
+ secure?: boolean;
705
+ /**
706
+ * SameSite policy (default: 'lax')
707
+ */
708
+ sameSite?: 'strict' | 'lax' | 'none';
709
+ }
710
+ /**
711
+ * Serialize cookie with options
712
+ */
713
+ declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
714
+ /**
715
+ * Parse cookie string into key-value pairs
716
+ */
717
+ declare function parseCookies(cookieHeader?: string): Record<string, string>;
718
+ /**
719
+ * Create a cookie header for clearing/deleting a cookie
720
+ */
721
+ declare function clearCookie(name: string, options?: Pick<CookieOptions, 'domain' | 'path'>): string;
722
+
723
+ /**
724
+ * RBAC Engine for checking roles and permissions
725
+ */
726
+ declare class RBACEngine {
727
+ private config;
728
+ private rolePermissions;
729
+ constructor(config: RBACConfig);
730
+ /**
731
+ * Build internal map of role → permissions for fast lookups
732
+ */
733
+ private buildRolePermissionMap;
734
+ /**
735
+ * Check if user has a specific role
736
+ */
737
+ hasRole(user: AuthUser, role: string): boolean;
738
+ /**
739
+ * Check if user has any of the specified roles
740
+ */
741
+ hasAnyRole(user: AuthUser, roles: string[]): boolean;
742
+ /**
743
+ * Check if user has all of the specified roles
744
+ */
745
+ hasAllRoles(user: AuthUser, roles: string[]): boolean;
746
+ /**
747
+ * Check if user has a specific permission
748
+ * Permissions are checked both:
749
+ * 1. Directly in user.permissions array
750
+ * 2. Through role-based permissions from config
751
+ */
752
+ hasPermission(user: AuthUser, permission: string): boolean;
753
+ /**
754
+ * Check if user has any of the specified permissions
755
+ */
756
+ hasAnyPermission(user: AuthUser, permissions: string[]): boolean;
757
+ /**
758
+ * Check if user has all of the specified permissions
759
+ */
760
+ hasAllPermissions(user: AuthUser, permissions: string[]): boolean;
761
+ /**
762
+ * Check if route is public (no auth required)
763
+ */
764
+ isPublicRoute(path: string): boolean;
765
+ /**
766
+ * Check if user can access a route based on protected_routes config
767
+ */
768
+ canAccessRoute(user: AuthUser | null, path: string): boolean;
769
+ /**
770
+ * Check if user can access a component based on component auth config
771
+ */
772
+ canAccessComponent(user: AuthUser | null, authConfig: ComponentAuthConfig | undefined): boolean;
773
+ private matchPath;
774
+ }
775
+
776
+ /**
777
+ * Auth context value provided to component tree
778
+ */
779
+ interface AuthContextValue {
780
+ /**
781
+ * Currently authenticated user (null if not authenticated)
782
+ */
783
+ user: AuthUser | null;
784
+ /**
785
+ * Current session (null if not authenticated)
786
+ */
787
+ session: AuthSession | null;
788
+ /**
789
+ * Whether user is authenticated
790
+ */
791
+ isAuthenticated: boolean;
792
+ /**
793
+ * Whether auth is still loading
794
+ */
795
+ isLoading: boolean;
796
+ /**
797
+ * Check if user has a specific role
798
+ */
799
+ hasRole: (role: string) => boolean;
800
+ /**
801
+ * Check if user has a specific permission
802
+ */
803
+ hasPermission: (permission: string) => boolean;
804
+ /**
805
+ * Check if user has any of the specified roles
806
+ */
807
+ hasAnyRole: (roles: string[]) => boolean;
808
+ /**
809
+ * Check if user has all of the specified permissions
810
+ */
811
+ hasAllPermissions: (permissions: string[]) => boolean;
812
+ }
813
+ /**
814
+ * Auth context - provides authentication state to components
815
+ */
816
+ declare const AuthContext: React.Context<AuthContextValue | null>;
817
+ /**
818
+ * Hook to access auth context
819
+ * Throws error if used outside AuthProvider
820
+ */
821
+ declare function useAuth(): AuthContextValue;
822
+ /**
823
+ * Hook to require authentication
824
+ * Returns null and logs warning if not authenticated
825
+ */
826
+ declare function useRequireAuth(): AuthContextValue | null;
827
+
828
+ interface AuthProviderProps {
829
+ /**
830
+ * Current authenticated user (null if not authenticated)
831
+ */
832
+ user: AuthUser | null;
833
+ /**
834
+ * Current session (null if not authenticated)
835
+ */
836
+ session: AuthSession | null;
837
+ /**
838
+ * RBAC configuration for role/permission checking
839
+ */
840
+ rbacConfig: RBACConfig;
841
+ /**
842
+ * Whether auth is still loading
843
+ */
844
+ isLoading?: boolean;
845
+ /**
846
+ * Child components
847
+ */
848
+ children: ReactNode;
849
+ }
850
+ /**
851
+ * AuthProvider - Provides authentication state to component tree
852
+ *
853
+ * @example
854
+ * ```tsx
855
+ * <AuthProvider user={user} session={session} rbacConfig={config}>
856
+ * <App />
857
+ * </AuthProvider>
858
+ * ```
859
+ */
860
+ declare function AuthProvider({ user, session, rbacConfig, isLoading, children, }: AuthProviderProps): ReactElement;
861
+
862
+ /**
863
+ * DoD CAC Profile Configuration
864
+ *
865
+ * Pre-built profile for DoD Common Access Card (CAC) certificates.
866
+ *
867
+ * This profile includes:
868
+ * - List of trusted DoD CA issuers
869
+ * - Required OU validation (DOD)
870
+ * - EDIPI extraction for user ID
871
+ *
872
+ * Last updated: 2025-01 (DoD PKI CA list)
873
+ *
874
+ * Note: DoD PKI infrastructure is regularly updated. Verify current CA list at:
875
+ * https://public.cyber.mil/pki-pke/
876
+ */
877
+
878
+ /**
879
+ * DoD CAC profile configuration
880
+ *
881
+ * This provides sensible defaults for DoD CAC authentication in most deployments.
882
+ * Assumes gateway (e.g., NGINX, HAProxy) handles mTLS and forwards headers.
883
+ */
884
+ declare const DOD_CAC_PROFILE: Omit<PKIConfig, 'type'>;
885
+ /**
886
+ * Create DoD CAC configuration with custom overrides
887
+ *
888
+ * @example
889
+ * ```typescript
890
+ * const config = createDoDCACConfig({
891
+ * source: 'direct_tls', // If terminating TLS in Node.js
892
+ * headerPrefix: 'ssl-client-', // Custom gateway header prefix
893
+ * });
894
+ * ```
895
+ */
896
+ declare function createDoDCACConfig(overrides?: Partial<PKIConfig>): PKIConfig;
897
+ /**
898
+ * Minimal DoD CAC config for development/testing
899
+ * Relaxes some requirements for local testing without real CAC cards
900
+ */
901
+ declare function createDoDCACDevConfig(): PKIConfig;
902
+
903
+ /**
904
+ * Props that any component wrapped with withAuth will receive
905
+ */
906
+ interface ComponentProps {
907
+ id?: string;
908
+ [key: string]: any;
909
+ }
910
+ /**
911
+ * Higher-order component that wraps a component with authentication checks
912
+ *
913
+ * @example
914
+ * ```tsx
915
+ * const ProtectedButton = withAuth(Button, {
916
+ * required_roles: ['ADMIN'],
917
+ * fallback: 'message',
918
+ * fallback_message: 'Only admins can see this button'
919
+ * });
920
+ * ```
921
+ *
922
+ * @param Component - The component to wrap
923
+ * @param authConfig - Authentication requirements from YAML
924
+ * @returns Wrapped component with auth enforcement
925
+ */
926
+ declare function withAuth<P extends ComponentProps>(Component: React__default.ComponentType<P>, authConfig?: ComponentAuthConfig): React__default.ComponentType<P>;
927
+ /**
928
+ * Custom fallback component (for advanced use cases)
929
+ */
930
+ declare function withAuthFallback<P extends ComponentProps>(Component: React__default.ComponentType<P>, authConfig: ComponentAuthConfig, FallbackComponent: React__default.ComponentType<any>): React__default.ComponentType<P>;
931
+
932
+ /**
933
+ * Register the auth decorator for use by content renderers
934
+ *
935
+ * This should be called once in your app's initialization (e.g., _app.tsx)
936
+ * to enable auth-aware component rendering.
937
+ *
938
+ * @example
939
+ * ```tsx
940
+ * // In pages/_app.tsx
941
+ * import { registerAuthDecorator } from '@stackwright-pro/auth';
942
+ *
943
+ * registerAuthDecorator();
944
+ *
945
+ * function MyApp({ Component, pageProps }: AppProps) {
946
+ * return (
947
+ * <AuthProvider {...authProps}>
948
+ * <Component {...pageProps} />
949
+ * </AuthProvider>
950
+ * );
951
+ * }
952
+ * ```
953
+ */
954
+ declare function registerAuthDecorator(): void;
955
+ /**
956
+ * Get the registered auth decorator (for use by content renderers)
957
+ *
958
+ * Returns null if auth is not registered, allowing graceful degradation.
959
+ * This is the function that OSS core would call to check if auth is available.
960
+ *
961
+ * @returns The withAuth decorator function, or null if not registered
962
+ */
963
+ declare function getAuthDecorator(): typeof withAuth | null;
964
+ /**
965
+ * Wrap a component with auth if decorator is registered and config exists
966
+ *
967
+ * This is a safe wrapper that OSS packages can use without depending on auth.
968
+ * If auth is not registered, returns the original component unchanged.
969
+ * If auth config is missing/undefined, returns the original component unchanged.
970
+ *
971
+ * @example
972
+ * ```tsx
973
+ * // In content renderer (can live in OSS core!)\n * function renderContentItem(item: ContentItem) {
974
+ * const Component = getComponentFromRegistry(item.type);
975
+ *
976
+ * // Apply auth if available
977
+ * const WrappedComponent = maybeWrapWithAuth(Component, item.auth);
978
+ *
979
+ * return <WrappedComponent {...item} />;
980
+ * }
981
+ * ```
982
+ *
983
+ * @param Component - Component to wrap
984
+ * @param authConfig - Auth configuration from YAML (optional)
985
+ * @returns Wrapped component if auth is registered, original component otherwise
986
+ */
987
+ declare function maybeWrapWithAuth<P extends {
988
+ id?: string;
989
+ [key: string]: any;
990
+ }>(Component: React__default.ComponentType<P>, authConfig?: ComponentAuthConfig): React__default.ComponentType<P>;
991
+ /**
992
+ * Type guard to check if content item has auth config
993
+ *
994
+ * Useful for conditionally applying auth in renderers without
995
+ * needing to import auth types.
996
+ *
997
+ * @example
998
+ * ```tsx
999
+ * if (hasAuthConfig(item)) {
1000
+ * // TypeScript knows item.auth exists
1001
+ * WrappedComponent = maybeWrapWithAuth(Component, item.auth);
1002
+ * }
1003
+ * ```
1004
+ */
1005
+ declare function hasAuthConfig(item: any): item is {
1006
+ auth: ComponentAuthConfig;
1007
+ };
1008
+
1009
+ export { type AuthConfig, AuthContext, type AuthContextValue, AuthProvider, type AuthProviderProps, type AuthSession, type AuthUser, type ComponentAuthConfig, type ComponentProps, type CookieOptions, DOD_CAC_PROFILE, KeycloakAdapter, type OIDCConfig, type OIDCMetadata, OIDCProvider, type PKIConfig, PKIProvider, type ParsedCertificate, type RBACConfig, RBACEngine, SessionManager, type SessionManagerConfig, type TokenSet, authConfigSchema, authSessionSchema, authUserSchema, buildAuthorizationUrl, clearCookie, componentAuthSchema, createDoDCACConfig, createDoDCACDevConfig, discoverOIDC, exchangeCodeForTokens, extractEDIPI, getAuthDecorator, hasAuthConfig, maybeWrapWithAuth, oidcConfigSchema, parseCertFromHeaders, parseCertificate, parseCookies, pkiConfigSchema, rbacConfigSchema, refreshAccessToken, registerAuthDecorator, serializeCookie, useAuth, useRequireAuth, validateDoDCAC, validateIdToken, withAuth, withAuthFallback };