@markwharton/pwa-core 1.7.0 → 2.0.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.
Files changed (103) hide show
  1. package/dist/{client/api.d.ts → client.d.ts} +85 -9
  2. package/dist/{client/api.js → client.js} +159 -56
  3. package/dist/index.d.ts +10 -2
  4. package/dist/index.js +14 -6
  5. package/dist/server.d.ts +283 -0
  6. package/dist/server.js +476 -0
  7. package/dist/shared.d.ts +150 -0
  8. package/dist/shared.js +124 -0
  9. package/package.json +11 -12
  10. package/dist/__tests__/auth/apiKey.test.d.ts +0 -1
  11. package/dist/__tests__/auth/apiKey.test.js +0 -80
  12. package/dist/__tests__/auth/token.test.d.ts +0 -1
  13. package/dist/__tests__/auth/token.test.js +0 -212
  14. package/dist/__tests__/auth/types.test.d.ts +0 -1
  15. package/dist/__tests__/auth/types.test.js +0 -77
  16. package/dist/__tests__/client/api.test.d.ts +0 -1
  17. package/dist/__tests__/client/api.test.js +0 -369
  18. package/dist/__tests__/client/apiError.test.d.ts +0 -1
  19. package/dist/__tests__/client/apiError.test.js +0 -91
  20. package/dist/__tests__/http/responses.test.d.ts +0 -1
  21. package/dist/__tests__/http/responses.test.js +0 -112
  22. package/dist/__tests__/http/status.test.d.ts +0 -1
  23. package/dist/__tests__/http/status.test.js +0 -27
  24. package/dist/__tests__/server/auth/apiKey.test.d.ts +0 -1
  25. package/dist/__tests__/server/auth/apiKey.test.js +0 -80
  26. package/dist/__tests__/server/auth/token.test.d.ts +0 -1
  27. package/dist/__tests__/server/auth/token.test.js +0 -299
  28. package/dist/__tests__/server/http/responses.test.d.ts +0 -1
  29. package/dist/__tests__/server/http/responses.test.js +0 -112
  30. package/dist/__tests__/server/storage/client.test.d.ts +0 -1
  31. package/dist/__tests__/server/storage/client.test.js +0 -173
  32. package/dist/__tests__/server/storage/keys.test.d.ts +0 -1
  33. package/dist/__tests__/server/storage/keys.test.js +0 -47
  34. package/dist/__tests__/shared/auth/types.test.d.ts +0 -1
  35. package/dist/__tests__/shared/auth/types.test.js +0 -77
  36. package/dist/__tests__/shared/http/status.test.d.ts +0 -1
  37. package/dist/__tests__/shared/http/status.test.js +0 -29
  38. package/dist/__tests__/storage/client.test.d.ts +0 -1
  39. package/dist/__tests__/storage/client.test.js +0 -173
  40. package/dist/__tests__/storage/keys.test.d.ts +0 -1
  41. package/dist/__tests__/storage/keys.test.js +0 -47
  42. package/dist/__tests__/types.test.d.ts +0 -1
  43. package/dist/__tests__/types.test.js +0 -56
  44. package/dist/auth/apiKey.d.ts +0 -44
  45. package/dist/auth/apiKey.js +0 -59
  46. package/dist/auth/index.d.ts +0 -3
  47. package/dist/auth/index.js +0 -22
  48. package/dist/auth/token.d.ts +0 -56
  49. package/dist/auth/token.js +0 -104
  50. package/dist/auth/types.d.ts +0 -63
  51. package/dist/auth/types.js +0 -41
  52. package/dist/client/apiError.d.ts +0 -48
  53. package/dist/client/apiError.js +0 -65
  54. package/dist/client/index.d.ts +0 -3
  55. package/dist/client/index.js +0 -14
  56. package/dist/client/types.d.ts +0 -12
  57. package/dist/client/types.js +0 -5
  58. package/dist/http/index.d.ts +0 -3
  59. package/dist/http/index.js +0 -14
  60. package/dist/http/responses.d.ts +0 -82
  61. package/dist/http/responses.js +0 -132
  62. package/dist/http/status.d.ts +0 -17
  63. package/dist/http/status.js +0 -19
  64. package/dist/http/types.d.ts +0 -10
  65. package/dist/http/types.js +0 -5
  66. package/dist/server/auth/apiKey.d.ts +0 -44
  67. package/dist/server/auth/apiKey.js +0 -59
  68. package/dist/server/auth/index.d.ts +0 -3
  69. package/dist/server/auth/index.js +0 -19
  70. package/dist/server/auth/token.d.ts +0 -102
  71. package/dist/server/auth/token.js +0 -158
  72. package/dist/server/http/index.d.ts +0 -1
  73. package/dist/server/http/index.js +0 -12
  74. package/dist/server/http/responses.d.ts +0 -82
  75. package/dist/server/http/responses.js +0 -132
  76. package/dist/server/index.d.ts +0 -4
  77. package/dist/server/index.js +0 -37
  78. package/dist/server/storage/client.d.ts +0 -48
  79. package/dist/server/storage/client.js +0 -107
  80. package/dist/server/storage/index.d.ts +0 -2
  81. package/dist/server/storage/index.js +0 -11
  82. package/dist/server/storage/keys.d.ts +0 -8
  83. package/dist/server/storage/keys.js +0 -14
  84. package/dist/shared/auth/index.d.ts +0 -2
  85. package/dist/shared/auth/index.js +0 -7
  86. package/dist/shared/auth/types.d.ts +0 -63
  87. package/dist/shared/auth/types.js +0 -41
  88. package/dist/shared/http/index.d.ts +0 -3
  89. package/dist/shared/http/index.js +0 -5
  90. package/dist/shared/http/status.d.ts +0 -19
  91. package/dist/shared/http/status.js +0 -21
  92. package/dist/shared/http/types.d.ts +0 -10
  93. package/dist/shared/http/types.js +0 -5
  94. package/dist/shared/index.d.ts +0 -5
  95. package/dist/shared/index.js +0 -10
  96. package/dist/storage/client.d.ts +0 -48
  97. package/dist/storage/client.js +0 -107
  98. package/dist/storage/index.d.ts +0 -2
  99. package/dist/storage/index.js +0 -11
  100. package/dist/storage/keys.d.ts +0 -8
  101. package/dist/storage/keys.js +0 -14
  102. package/dist/types.d.ts +0 -48
  103. package/dist/types.js +0 -41
@@ -0,0 +1,283 @@
1
+ /**
2
+ * pwa-core/server - Server-side utilities for Azure Functions
3
+ *
4
+ * Includes: JWT auth, API keys, HTTP responses, Azure Table Storage
5
+ */
6
+ import { HttpResponseInit, InvocationContext } from '@azure/functions';
7
+ import { TableClient } from '@azure/data-tables';
8
+ import { Result, BaseJwtPayload, RoleTokenPayload } from './shared';
9
+ /**
10
+ * Initializes the JWT authentication system. Call once at application startup.
11
+ * @param secret - The JWT secret key (from environment variable)
12
+ * @param minLength - Minimum required secret length (default: 32)
13
+ * @throws Error if secret is missing or too short
14
+ * @example
15
+ * initAuth(process.env.JWT_SECRET);
16
+ */
17
+ export declare function initAuth(secret: string | undefined, minLength?: number): void;
18
+ /**
19
+ * Gets the configured JWT secret.
20
+ * @returns The JWT secret string
21
+ * @throws Error if initAuth() has not been called
22
+ */
23
+ export declare function getJwtSecret(): string;
24
+ /**
25
+ * Extracts the Bearer token from an Authorization header.
26
+ * @param authHeader - The Authorization header value
27
+ * @returns The token string, or null if not a valid Bearer token
28
+ * @example
29
+ * const token = extractToken(request.headers.get('Authorization'));
30
+ */
31
+ export declare function extractToken(authHeader: string | null): string | null;
32
+ /**
33
+ * Validates and decodes a JWT token.
34
+ * @typeParam T - The expected payload type (extends object)
35
+ * @param token - The JWT token string to validate
36
+ * @returns Result with decoded payload on success, or error message on failure
37
+ * @example
38
+ * const result = validateToken<UserPayload>(token);
39
+ * if (result.ok) {
40
+ * console.log(result.data.username);
41
+ * }
42
+ */
43
+ export declare function validateToken<T extends object>(token: string): Result<T>;
44
+ /**
45
+ * Generates a signed JWT token with the given payload.
46
+ * @typeParam T - The payload type (extends object)
47
+ * @param payload - The data to encode in the token
48
+ * @param expiresIn - Token expiration time (default: '7d')
49
+ * @returns The signed JWT token string
50
+ * @example
51
+ * const token = generateToken({ userId: '123', role: 'admin' }, '1h');
52
+ */
53
+ export declare function generateToken<T extends object>(payload: T, expiresIn?: string): string;
54
+ /**
55
+ * Generates a long-lived JWT token for machine/API access.
56
+ * @typeParam T - The payload type (extends object)
57
+ * @param payload - The data to encode in the token
58
+ * @param expiresInDays - Token expiration in days (default: 3650 = 10 years)
59
+ * @returns The signed JWT token string
60
+ * @example
61
+ * const apiToken = generateLongLivedToken({ machineId: 'server-1' });
62
+ */
63
+ export declare function generateLongLivedToken<T extends object>(payload: T, expiresInDays?: number): string;
64
+ /**
65
+ * Successful auth result with typed payload.
66
+ */
67
+ export interface AuthSuccess<T extends BaseJwtPayload> {
68
+ authorized: true;
69
+ payload: T;
70
+ }
71
+ /**
72
+ * Failed auth result with HTTP response.
73
+ */
74
+ export interface AuthFailure {
75
+ authorized: false;
76
+ response: HttpResponseInit;
77
+ }
78
+ /**
79
+ * Discriminated union for auth results.
80
+ * Use `auth.authorized` to narrow the type.
81
+ */
82
+ export type AuthResult<T extends BaseJwtPayload> = AuthSuccess<T> | AuthFailure;
83
+ /** Default token expiry string for generateToken (7 days) */
84
+ export declare const DEFAULT_TOKEN_EXPIRY = "7d";
85
+ /** Default token expiry in seconds (7 days = 604800 seconds) */
86
+ export declare const DEFAULT_TOKEN_EXPIRY_SECONDS: number;
87
+ /**
88
+ * Validates auth header and returns typed payload or error response.
89
+ * @typeParam T - The expected payload type (extends BaseJwtPayload)
90
+ * @param authHeader - The Authorization header value
91
+ * @returns AuthResult with payload on success, or HTTP response on failure
92
+ * @example
93
+ * const auth = requireAuth<UsernameTokenPayload>(request.headers.get('Authorization'));
94
+ * if (!auth.authorized) return auth.response;
95
+ * console.log(auth.payload.username);
96
+ */
97
+ export declare function requireAuth<T extends BaseJwtPayload>(authHeader: string | null): AuthResult<T>;
98
+ /**
99
+ * Requires admin role. Use with RoleTokenPayload.
100
+ * @param authHeader - The Authorization header value
101
+ * @returns AuthResult with RoleTokenPayload on success, or HTTP response on failure
102
+ * @example
103
+ * const auth = requireAdmin(request.headers.get('Authorization'));
104
+ * if (!auth.authorized) return auth.response;
105
+ * // User is admin
106
+ */
107
+ export declare function requireAdmin(authHeader: string | null): AuthResult<RoleTokenPayload>;
108
+ /**
109
+ * Extracts API key from the X-API-Key header.
110
+ * @param request - Request object with headers.get() method
111
+ * @returns The API key string, or null if not present
112
+ * @example
113
+ * const apiKey = extractApiKey(request);
114
+ */
115
+ export declare function extractApiKey(request: {
116
+ headers: {
117
+ get(name: string): string | null;
118
+ };
119
+ }): string | null;
120
+ /**
121
+ * Hashes an API key using SHA-256 for secure storage.
122
+ * Store this hash in your database, never the raw key.
123
+ * @param apiKey - The raw API key to hash
124
+ * @returns The SHA-256 hash as a hex string
125
+ * @example
126
+ * const hash = hashApiKey(rawKey);
127
+ * await db.save({ apiKeyHash: hash });
128
+ */
129
+ export declare function hashApiKey(apiKey: string): string;
130
+ /**
131
+ * Validates an API key against a stored hash.
132
+ * @param apiKey - The API key from the request
133
+ * @param storedHash - The hash stored in your database
134
+ * @returns Result with ok=true if valid, or error message if invalid
135
+ * @example
136
+ * const result = validateApiKey(apiKey, user.apiKeyHash);
137
+ * if (!result.ok) return httpUnauthorized();
138
+ */
139
+ export declare function validateApiKey(apiKey: string, storedHash: string): Result<void>;
140
+ /**
141
+ * Generates a cryptographically secure API key.
142
+ * @returns A random 64-character hex string (32 bytes)
143
+ * @example
144
+ * const apiKey = generateApiKey();
145
+ * // Return to user once, store hash in database
146
+ */
147
+ export declare function generateApiKey(): string;
148
+ /**
149
+ * Creates a 400 Bad Request response.
150
+ * @param message - The error message to return
151
+ * @returns Azure Functions HttpResponseInit object
152
+ * @example
153
+ * if (!body.email) return badRequestResponse('Email is required');
154
+ */
155
+ export declare function badRequestResponse(message: string): HttpResponseInit;
156
+ /**
157
+ * Creates a 401 Unauthorized response.
158
+ * @param message - The error message (default: 'Unauthorized')
159
+ * @returns Azure Functions HttpResponseInit object
160
+ * @example
161
+ * if (!token) return unauthorizedResponse();
162
+ */
163
+ export declare function unauthorizedResponse(message?: string): HttpResponseInit;
164
+ /**
165
+ * Creates a 403 Forbidden response.
166
+ * @param message - The error message (default: 'Forbidden')
167
+ * @returns Azure Functions HttpResponseInit object
168
+ * @example
169
+ * if (!isAdmin(payload)) return forbiddenResponse('Admin access required');
170
+ */
171
+ export declare function forbiddenResponse(message?: string): HttpResponseInit;
172
+ /**
173
+ * Creates a 404 Not Found response.
174
+ * @param resource - The name of the resource that wasn't found
175
+ * @returns Azure Functions HttpResponseInit object
176
+ * @example
177
+ * if (!user) return notFoundResponse('User');
178
+ * // Returns: { error: 'User not found' }
179
+ */
180
+ export declare function notFoundResponse(resource: string): HttpResponseInit;
181
+ /**
182
+ * Creates a 409 Conflict response.
183
+ * @param message - The conflict error message
184
+ * @returns Azure Functions HttpResponseInit object
185
+ * @example
186
+ * if (existingUser) return conflictResponse('Email already registered');
187
+ */
188
+ export declare function conflictResponse(message: string): HttpResponseInit;
189
+ /**
190
+ * Handles unexpected errors safely by logging details and returning a generic message.
191
+ * Use in catch blocks to avoid exposing internal error details to clients.
192
+ * @param error - The caught error
193
+ * @param context - Azure Functions InvocationContext for logging
194
+ * @returns Azure Functions HttpResponseInit with 500 status
195
+ * @example
196
+ * try {
197
+ * await riskyOperation();
198
+ * } catch (error) {
199
+ * return handleFunctionError(error, context);
200
+ * }
201
+ */
202
+ export declare function handleFunctionError(error: unknown, context: InvocationContext): HttpResponseInit;
203
+ /**
204
+ * Checks if an error is an Azure Table Storage "not found" error.
205
+ * @param error - The caught error
206
+ * @returns True if error has statusCode 404
207
+ * @example
208
+ * try {
209
+ * await tableClient.getEntity(pk, rk);
210
+ * } catch (error) {
211
+ * if (isNotFoundError(error)) return notFoundResponse('Entity');
212
+ * throw error;
213
+ * }
214
+ */
215
+ export declare function isNotFoundError(error: unknown): boolean;
216
+ /**
217
+ * Checks if an error is an Azure Table Storage "conflict" error.
218
+ * @param error - The caught error
219
+ * @returns True if error has statusCode 409
220
+ * @example
221
+ * try {
222
+ * await tableClient.createEntity(entity);
223
+ * } catch (error) {
224
+ * if (isConflictError(error)) return conflictResponse('Entity already exists');
225
+ * throw error;
226
+ * }
227
+ */
228
+ export declare function isConflictError(error: unknown): boolean;
229
+ /**
230
+ * Initializes Azure Table Storage configuration. Call once at application startup.
231
+ * @param config - Storage configuration options
232
+ * @param config.accountName - Azure Storage account name (for managed identity)
233
+ * @param config.connectionString - Connection string (for local development)
234
+ * @example
235
+ * // Production (Azure with managed identity)
236
+ * initStorage({ accountName: 'mystorageaccount' });
237
+ *
238
+ * // Local development (Azurite)
239
+ * initStorage({ connectionString: 'UseDevelopmentStorage=true' });
240
+ */
241
+ export declare function initStorage(config: {
242
+ accountName?: string;
243
+ connectionString?: string;
244
+ }): void;
245
+ /**
246
+ * Initializes storage from environment variables.
247
+ * Reads STORAGE_ACCOUNT_NAME and AzureWebJobsStorage.
248
+ * @example
249
+ * initStorageFromEnv(); // Uses process.env automatically
250
+ */
251
+ export declare function initStorageFromEnv(): void;
252
+ /**
253
+ * Checks if using Azure managed identity vs local connection string.
254
+ * @returns True if using managed identity (production), false for connection string (local)
255
+ */
256
+ export declare function useManagedIdentity(): boolean;
257
+ /**
258
+ * Gets a TableClient for the specified table, creating the table if needed.
259
+ * Clients are cached for reuse across requests.
260
+ * @param tableName - The Azure Table Storage table name
261
+ * @returns A configured TableClient instance
262
+ * @throws Error if storage is not configured
263
+ * @example
264
+ * const client = await getTableClient('users');
265
+ * await client.createEntity({ partitionKey: 'pk', rowKey: 'rk', name: 'John' });
266
+ */
267
+ export declare function getTableClient(tableName: string): Promise<TableClient>;
268
+ /**
269
+ * Clears the TableClient cache. Primarily useful for testing.
270
+ * @example
271
+ * afterEach(() => {
272
+ * clearTableClientCache();
273
+ * });
274
+ */
275
+ export declare function clearTableClientCache(): void;
276
+ /**
277
+ * Generate a unique row key from an identifier string.
278
+ * Uses SHA-256 hash for consistent, URL-safe keys.
279
+ * @param identifier - The string to hash (e.g., subscription endpoint, user ID)
280
+ * @returns A 32-character hex string suitable for Azure Table Storage row keys
281
+ */
282
+ export declare function generateRowKey(identifier: string): string;
283
+ export { Result, ok, okVoid, err, getErrorMessage, BaseJwtPayload, UserTokenPayload, UsernameTokenPayload, RoleTokenPayload, hasUsername, hasRole, isAdmin, HTTP_STATUS, HttpStatus, ErrorResponse } from './shared';