@markwharton/pwa-core 3.2.1 → 3.4.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/dist/client.d.ts +116 -0
- package/dist/client.js +270 -0
- package/dist/server.d.ts +313 -6
- package/dist/server.js +697 -3
- package/dist/shared.d.ts +40 -0
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Includes: JWT auth, API keys, HTTP responses, Azure Table Storage
|
|
5
5
|
*/
|
|
6
|
-
import { HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
6
|
+
import { HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
7
7
|
import { TableClient } from '@azure/data-tables';
|
|
8
|
-
import { Result, BaseJwtPayload, RoleTokenPayload } from './shared';
|
|
8
|
+
import { Result, BaseJwtPayload, RoleTokenPayload, SessionUser, SessionInfo } from './shared';
|
|
9
9
|
/**
|
|
10
10
|
* Initializes the JWT authentication system. Call once at application startup.
|
|
11
11
|
* @param config - Configuration object
|
|
@@ -200,6 +200,30 @@ export declare function notFoundResponse(resource: string): HttpResponseInit;
|
|
|
200
200
|
* if (existingUser) return conflictResponse('Email already registered');
|
|
201
201
|
*/
|
|
202
202
|
export declare function conflictResponse(message: string): HttpResponseInit;
|
|
203
|
+
/**
|
|
204
|
+
* Creates a 410 Gone response.
|
|
205
|
+
* @param message - The error message to return
|
|
206
|
+
* @returns Azure Functions HttpResponseInit object
|
|
207
|
+
* @example
|
|
208
|
+
* if (linkUsed) return goneResponse('Link already used');
|
|
209
|
+
*/
|
|
210
|
+
export declare function goneResponse(message: string): HttpResponseInit;
|
|
211
|
+
/**
|
|
212
|
+
* Creates a 429 Too Many Requests response.
|
|
213
|
+
* @param message - The error message to return
|
|
214
|
+
* @returns Azure Functions HttpResponseInit object
|
|
215
|
+
* @example
|
|
216
|
+
* if (rateLimited) return tooManyRequestsResponse('Too many requests');
|
|
217
|
+
*/
|
|
218
|
+
export declare function tooManyRequestsResponse(message: string): HttpResponseInit;
|
|
219
|
+
/**
|
|
220
|
+
* Creates a 503 Service Unavailable response.
|
|
221
|
+
* @param message - The error message to return
|
|
222
|
+
* @returns Azure Functions HttpResponseInit object
|
|
223
|
+
* @example
|
|
224
|
+
* if (!emailSent) return serviceUnavailableResponse('Email service unavailable');
|
|
225
|
+
*/
|
|
226
|
+
export declare function serviceUnavailableResponse(message: string): HttpResponseInit;
|
|
203
227
|
/**
|
|
204
228
|
* Validates that a required parameter is present.
|
|
205
229
|
* Returns an error response if missing, null if valid.
|
|
@@ -235,12 +259,12 @@ export declare function initErrorHandling(config?: {
|
|
|
235
259
|
* Initializes error handling from environment variables.
|
|
236
260
|
* Currently a no-op but provides consistent API for future error config
|
|
237
261
|
* (e.g., ERROR_LOG_LEVEL, ERROR_INCLUDE_STACK).
|
|
262
|
+
* @param callback - Optional callback invoked when handleFunctionError is called (fire-and-forget)
|
|
238
263
|
* @example
|
|
239
264
|
* initErrorHandlingFromEnv(); // Uses process.env automatically
|
|
265
|
+
* initErrorHandlingFromEnv((op, msg) => sendAlert(op, msg));
|
|
240
266
|
*/
|
|
241
|
-
export declare function initErrorHandlingFromEnv(
|
|
242
|
-
callback?: ErrorCallback;
|
|
243
|
-
}): void;
|
|
267
|
+
export declare function initErrorHandlingFromEnv(callback?: ErrorCallback): void;
|
|
244
268
|
/**
|
|
245
269
|
* Handles unexpected errors safely by logging details and returning a generic message.
|
|
246
270
|
* Use in catch blocks to avoid exposing internal error details to clients.
|
|
@@ -359,6 +383,50 @@ export declare function clearTableClientCache(): void;
|
|
|
359
383
|
* if (!user) return notFoundResponse('User');
|
|
360
384
|
*/
|
|
361
385
|
export declare function getEntityIfExists<T extends object>(client: TableClient, partitionKey: string, rowKey: string): Promise<T | null>;
|
|
386
|
+
/**
|
|
387
|
+
* Upserts (insert or replace) an entity in Azure Table Storage.
|
|
388
|
+
* @typeParam T - The entity type with partitionKey and rowKey
|
|
389
|
+
* @param client - The TableClient instance
|
|
390
|
+
* @param entity - The entity to upsert
|
|
391
|
+
* @example
|
|
392
|
+
* await upsertEntity(client, { partitionKey: 'user', rowKey: 'john', name: 'John' });
|
|
393
|
+
*/
|
|
394
|
+
export declare function upsertEntity<T extends {
|
|
395
|
+
partitionKey: string;
|
|
396
|
+
rowKey: string;
|
|
397
|
+
}>(client: TableClient, entity: T): Promise<void>;
|
|
398
|
+
/**
|
|
399
|
+
* Deletes an entity from Azure Table Storage.
|
|
400
|
+
* Returns true on success, false on error (swallows errors).
|
|
401
|
+
* @param client - The TableClient instance
|
|
402
|
+
* @param partitionKey - The partition key
|
|
403
|
+
* @param rowKey - The row key
|
|
404
|
+
* @returns True if deleted successfully, false on error
|
|
405
|
+
* @example
|
|
406
|
+
* const deleted = await deleteEntity(client, 'session', sessionId);
|
|
407
|
+
*/
|
|
408
|
+
export declare function deleteEntity(client: TableClient, partitionKey: string, rowKey: string): Promise<boolean>;
|
|
409
|
+
/**
|
|
410
|
+
* Lists all entities in a partition.
|
|
411
|
+
* @typeParam T - The entity type
|
|
412
|
+
* @param client - The TableClient instance
|
|
413
|
+
* @param partitionKey - The partition key to filter by
|
|
414
|
+
* @returns Array of entities in the partition
|
|
415
|
+
* @example
|
|
416
|
+
* const sessions = await listEntitiesByPartition<SessionEntity>(client, 'session');
|
|
417
|
+
*/
|
|
418
|
+
export declare function listEntitiesByPartition<T extends object>(client: TableClient, partitionKey: string): Promise<T[]>;
|
|
419
|
+
/**
|
|
420
|
+
* Lists entities matching a custom OData filter.
|
|
421
|
+
* @typeParam T - The entity type
|
|
422
|
+
* @param client - The TableClient instance
|
|
423
|
+
* @param filter - OData filter string (use the odata template tag from @azure/data-tables)
|
|
424
|
+
* @returns Array of matching entities
|
|
425
|
+
* @example
|
|
426
|
+
* const filter = odata`PartitionKey eq ${'user'} and email eq ${'john@example.com'}`;
|
|
427
|
+
* const users = await listEntitiesWithFilter<UserEntity>(client, filter);
|
|
428
|
+
*/
|
|
429
|
+
export declare function listEntitiesWithFilter<T extends object>(client: TableClient, filter: string): Promise<T[]>;
|
|
362
430
|
/**
|
|
363
431
|
* Generate a unique row key from an identifier string.
|
|
364
432
|
* Uses SHA-256 hash for consistent, URL-safe keys.
|
|
@@ -366,4 +434,243 @@ export declare function getEntityIfExists<T extends object>(client: TableClient,
|
|
|
366
434
|
* @returns A 32-character hex string suitable for Azure Table Storage row keys
|
|
367
435
|
*/
|
|
368
436
|
export declare function generateRowKey(identifier: string): string;
|
|
369
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Configuration for session-based authentication.
|
|
439
|
+
*/
|
|
440
|
+
export interface SessionAuthConfig {
|
|
441
|
+
/** Cookie name for the session (default: 'app_session') */
|
|
442
|
+
cookieName?: string;
|
|
443
|
+
/** Session duration in ms (default: 30 days) */
|
|
444
|
+
sessionDurationMs?: number;
|
|
445
|
+
/** Refresh threshold in ms - refresh session if within this time of expiry (default: 24 hours) */
|
|
446
|
+
sessionRefreshThresholdMs?: number;
|
|
447
|
+
/** Magic link token expiry in ms (default: 15 minutes) */
|
|
448
|
+
magicLinkExpiryMs?: number;
|
|
449
|
+
/** Rate limit window in ms (default: 1 hour) */
|
|
450
|
+
rateLimitWindowMs?: number;
|
|
451
|
+
/** Max magic link requests per email per window (default: 5) */
|
|
452
|
+
rateLimitMaxRequests?: number;
|
|
453
|
+
/** SameSite cookie attribute (default: 'Strict') */
|
|
454
|
+
sameSite?: 'Strict' | 'Lax';
|
|
455
|
+
/** Specific emails allowed (empty = allow all) */
|
|
456
|
+
allowedEmails?: string[];
|
|
457
|
+
/** Domain allowlist suffix (e.g., '@company.com') */
|
|
458
|
+
allowedDomain?: string;
|
|
459
|
+
/** Emails that get isAdmin=true */
|
|
460
|
+
adminEmails?: string[];
|
|
461
|
+
/** Base URL for magic links and SWA preview URL validation */
|
|
462
|
+
appBaseUrl?: string;
|
|
463
|
+
/** Required callback to send magic link emails */
|
|
464
|
+
sendEmail: (to: string, magicLink: string) => Promise<boolean>;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Initializes session-based authentication. Call once at application startup.
|
|
468
|
+
* @param config - Session auth configuration
|
|
469
|
+
* @throws Error if sendEmail callback is not provided
|
|
470
|
+
* @example
|
|
471
|
+
* initSessionAuth({
|
|
472
|
+
* sendEmail: async (to, magicLink) => { await resend.emails.send(...); return true; },
|
|
473
|
+
* allowedEmails: ['admin@example.com'],
|
|
474
|
+
* appBaseUrl: 'https://myapp.com'
|
|
475
|
+
* });
|
|
476
|
+
*/
|
|
477
|
+
export declare function initSessionAuth(config: SessionAuthConfig): void;
|
|
478
|
+
/**
|
|
479
|
+
* Initializes session auth from environment variables.
|
|
480
|
+
* Reads: SESSION_COOKIE_NAME, APP_BASE_URL, ALLOWED_EMAILS, ALLOWED_DOMAIN, ADMIN_EMAILS.
|
|
481
|
+
* @param sendEmail - Required callback to send magic link emails
|
|
482
|
+
* @throws Error if sendEmail is not provided
|
|
483
|
+
* @example
|
|
484
|
+
* initSessionAuthFromEnv(async (to, magicLink) => {
|
|
485
|
+
* await resend.emails.send({ to, html: `<a href="${magicLink}">Sign In</a>` });
|
|
486
|
+
* return true;
|
|
487
|
+
* });
|
|
488
|
+
*/
|
|
489
|
+
export declare function initSessionAuthFromEnv(sendEmail: (to: string, magicLink: string) => Promise<boolean>): void;
|
|
490
|
+
/**
|
|
491
|
+
* Parses cookies from a request's Cookie header.
|
|
492
|
+
* @param request - Request object with headers.get() method
|
|
493
|
+
* @returns Record of cookie name/value pairs
|
|
494
|
+
* @example
|
|
495
|
+
* const cookies = parseCookies(request);
|
|
496
|
+
* const sessionId = cookies['app_session'];
|
|
497
|
+
*/
|
|
498
|
+
export declare function parseCookies(request: {
|
|
499
|
+
headers: {
|
|
500
|
+
get(name: string): string | null;
|
|
501
|
+
};
|
|
502
|
+
}): Record<string, string>;
|
|
503
|
+
/**
|
|
504
|
+
* Creates a Set-Cookie header value for a session cookie.
|
|
505
|
+
* @param sessionId - The session ID to store in the cookie
|
|
506
|
+
* @returns Cookie string for the Set-Cookie header
|
|
507
|
+
* @example
|
|
508
|
+
* return { headers: { 'Set-Cookie': createSessionCookie(sessionId) } };
|
|
509
|
+
*/
|
|
510
|
+
export declare function createSessionCookie(sessionId: string): string;
|
|
511
|
+
/**
|
|
512
|
+
* Creates a Set-Cookie header value that clears the session cookie.
|
|
513
|
+
* @returns Cookie string for the Set-Cookie header
|
|
514
|
+
* @example
|
|
515
|
+
* return { headers: { 'Set-Cookie': createLogoutCookie() } };
|
|
516
|
+
*/
|
|
517
|
+
export declare function createLogoutCookie(): string;
|
|
518
|
+
/**
|
|
519
|
+
* Validates a request origin for SWA preview/staging deployments.
|
|
520
|
+
* Returns the trusted origin if it matches the configured appBaseUrl:
|
|
521
|
+
* - Exact hostname match (production)
|
|
522
|
+
* - Localhost-to-localhost match (local development)
|
|
523
|
+
* - App name prefix match for SWA preview slots
|
|
524
|
+
* @param origin - The request Origin header value
|
|
525
|
+
* @returns The trusted origin URL, or undefined if not trusted
|
|
526
|
+
* @example
|
|
527
|
+
* const trusted = getTrustedOrigin(request.headers.get('origin'));
|
|
528
|
+
* const baseUrl = trusted ?? config.appBaseUrl;
|
|
529
|
+
*/
|
|
530
|
+
export declare function getTrustedOrigin(origin: string | null): string | undefined;
|
|
531
|
+
/**
|
|
532
|
+
* Validates an email address format.
|
|
533
|
+
* @param email - The email address to validate
|
|
534
|
+
* @returns True if the email format is valid
|
|
535
|
+
*/
|
|
536
|
+
export declare function isValidEmail(email: string): boolean;
|
|
537
|
+
/**
|
|
538
|
+
* Checks if an email is allowed by the configured allowlist.
|
|
539
|
+
* Checks both allowedEmails (exact match) and allowedDomain (suffix match).
|
|
540
|
+
* If neither is configured, allows all emails.
|
|
541
|
+
* @param email - The email address to check
|
|
542
|
+
* @returns True if the email is allowed
|
|
543
|
+
*/
|
|
544
|
+
export declare function isEmailAllowed(email: string): boolean;
|
|
545
|
+
/**
|
|
546
|
+
* Checks if a magic link request is within rate limits.
|
|
547
|
+
* @param email - The email address to check
|
|
548
|
+
* @returns True if within limits, false if rate limited
|
|
549
|
+
*/
|
|
550
|
+
export declare function checkMagicLinkRateLimit(email: string): Promise<boolean>;
|
|
551
|
+
/**
|
|
552
|
+
* Creates a magic link token, stores it, and sends an email.
|
|
553
|
+
* @param email - The email address to send the magic link to
|
|
554
|
+
* @param request - Request object (used to resolve SWA preview origin)
|
|
555
|
+
* @returns Result with the token on success, or error with statusCode
|
|
556
|
+
* @example
|
|
557
|
+
* const result = await createMagicLink(email, request);
|
|
558
|
+
* if (!result.ok) return { status: result.statusCode, jsonBody: { error: result.error } };
|
|
559
|
+
*/
|
|
560
|
+
export declare function createMagicLink(email: string, request: {
|
|
561
|
+
headers: {
|
|
562
|
+
get(name: string): string | null;
|
|
563
|
+
};
|
|
564
|
+
}): Promise<Result<string>>;
|
|
565
|
+
/**
|
|
566
|
+
* Verifies a magic link token, creates/updates the user, and creates a session.
|
|
567
|
+
* @param token - The magic link token to verify
|
|
568
|
+
* @returns Result with user and sessionCookie on success, or error with statusCode
|
|
569
|
+
* @example
|
|
570
|
+
* const result = await verifyMagicLink(token);
|
|
571
|
+
* if (!result.ok) return { status: result.statusCode, jsonBody: { error: result.error } };
|
|
572
|
+
* return { headers: { 'Set-Cookie': result.data.sessionCookie }, jsonBody: { user: result.data.user } };
|
|
573
|
+
*/
|
|
574
|
+
export declare function verifyMagicLink(token: string): Promise<Result<{
|
|
575
|
+
user: SessionUser;
|
|
576
|
+
sessionCookie: string;
|
|
577
|
+
}>>;
|
|
578
|
+
/**
|
|
579
|
+
* Validates a session cookie and returns the user and session info.
|
|
580
|
+
* Performs sliding window refresh if the session is close to expiry.
|
|
581
|
+
* @param request - Request object with headers.get() method
|
|
582
|
+
* @returns User, session, and optional refreshed cookie, or null if invalid
|
|
583
|
+
* @example
|
|
584
|
+
* const result = await validateSession(request);
|
|
585
|
+
* if (!result) return unauthorizedResponse();
|
|
586
|
+
*/
|
|
587
|
+
export declare function validateSession(request: {
|
|
588
|
+
headers: {
|
|
589
|
+
get(name: string): string | null;
|
|
590
|
+
};
|
|
591
|
+
}): Promise<{
|
|
592
|
+
user: SessionUser;
|
|
593
|
+
session: SessionInfo;
|
|
594
|
+
refreshedCookie?: string;
|
|
595
|
+
} | null>;
|
|
596
|
+
/**
|
|
597
|
+
* Convenience function: validates session and returns user with optional refresh headers.
|
|
598
|
+
* @param request - Request object with headers.get() method
|
|
599
|
+
* @returns User and optional Set-Cookie headers, or null if not authenticated
|
|
600
|
+
* @example
|
|
601
|
+
* const result = await getSessionUser(request);
|
|
602
|
+
* if (!result) return unauthorizedResponse();
|
|
603
|
+
* return { headers: result.headers, jsonBody: { user: result.user } };
|
|
604
|
+
*/
|
|
605
|
+
export declare function getSessionUser(request: {
|
|
606
|
+
headers: {
|
|
607
|
+
get(name: string): string | null;
|
|
608
|
+
};
|
|
609
|
+
}): Promise<{
|
|
610
|
+
user: SessionUser;
|
|
611
|
+
headers?: Record<string, string>;
|
|
612
|
+
} | null>;
|
|
613
|
+
/**
|
|
614
|
+
* Destroys the current session and returns a logout cookie string.
|
|
615
|
+
* @param request - Request object with headers.get() method
|
|
616
|
+
* @returns Cookie string for the Set-Cookie header
|
|
617
|
+
* @example
|
|
618
|
+
* const cookie = await destroySession(request);
|
|
619
|
+
* return { headers: { 'Set-Cookie': cookie }, jsonBody: { success: true } };
|
|
620
|
+
*/
|
|
621
|
+
export declare function destroySession(request: {
|
|
622
|
+
headers: {
|
|
623
|
+
get(name: string): string | null;
|
|
624
|
+
};
|
|
625
|
+
}): Promise<string>;
|
|
626
|
+
/**
|
|
627
|
+
* Wraps a handler with session authentication.
|
|
628
|
+
* Returns 401 if not authenticated. Automatically handles session refresh cookies.
|
|
629
|
+
* @param handler - The handler function that receives the authenticated user
|
|
630
|
+
* @returns A wrapped handler function
|
|
631
|
+
* @example
|
|
632
|
+
* app.http('myEndpoint', {
|
|
633
|
+
* handler: withSessionAuth(async (request, context, auth) => {
|
|
634
|
+
* return { jsonBody: { user: auth.user } };
|
|
635
|
+
* })
|
|
636
|
+
* });
|
|
637
|
+
*/
|
|
638
|
+
export declare function withSessionAuth(handler: (request: HttpRequest, context: InvocationContext, auth: {
|
|
639
|
+
user: SessionUser;
|
|
640
|
+
headers?: Record<string, string>;
|
|
641
|
+
}) => Promise<HttpResponseInit>): (request: HttpRequest, context: InvocationContext) => Promise<HttpResponseInit>;
|
|
642
|
+
/**
|
|
643
|
+
* Wraps a handler with session admin authentication.
|
|
644
|
+
* Returns 401 if not authenticated, 403 if not admin.
|
|
645
|
+
* @param handler - The handler function that receives the authenticated admin user
|
|
646
|
+
* @returns A wrapped handler function
|
|
647
|
+
* @example
|
|
648
|
+
* app.http('adminEndpoint', {
|
|
649
|
+
* handler: withSessionAdminAuth(async (request, context, auth) => {
|
|
650
|
+
* return { jsonBody: { admin: auth.user.email } };
|
|
651
|
+
* })
|
|
652
|
+
* });
|
|
653
|
+
*/
|
|
654
|
+
export declare function withSessionAdminAuth(handler: (request: HttpRequest, context: InvocationContext, auth: {
|
|
655
|
+
user: SessionUser;
|
|
656
|
+
headers?: Record<string, string>;
|
|
657
|
+
}) => Promise<HttpResponseInit>): (request: HttpRequest, context: InvocationContext) => Promise<HttpResponseInit>;
|
|
658
|
+
/**
|
|
659
|
+
* Deletes expired sessions from storage. Call periodically (e.g., timer trigger).
|
|
660
|
+
* @returns Number of sessions deleted
|
|
661
|
+
* @example
|
|
662
|
+
* // Timer trigger
|
|
663
|
+
* const deleted = await deleteExpiredSessions();
|
|
664
|
+
* context.log(`Cleaned up ${deleted} expired sessions`);
|
|
665
|
+
*/
|
|
666
|
+
export declare function deleteExpiredSessions(): Promise<number>;
|
|
667
|
+
/**
|
|
668
|
+
* Deletes expired magic links from storage. Call periodically (e.g., timer trigger).
|
|
669
|
+
* @returns Number of magic links deleted
|
|
670
|
+
* @example
|
|
671
|
+
* // Timer trigger
|
|
672
|
+
* const deleted = await deleteExpiredMagicLinks();
|
|
673
|
+
* context.log(`Cleaned up ${deleted} expired magic links`);
|
|
674
|
+
*/
|
|
675
|
+
export declare function deleteExpiredMagicLinks(): Promise<number>;
|
|
676
|
+
export { Result, ok, okVoid, err, getErrorMessage, BaseJwtPayload, UserTokenPayload, UsernameTokenPayload, RoleTokenPayload, hasUsername, hasRole, isAdmin, HTTP_STATUS, HttpStatus, ErrorResponse, SessionUser, SessionInfo, MagicLinkRequest, SessionAuthResponse } from './shared';
|