@markwharton/pwa-core 1.2.1 → 1.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/__tests__/auth/token.test.js +23 -0
- package/dist/__tests__/client/api.test.js +65 -0
- package/dist/__tests__/server/auth/apiKey.test.d.ts +1 -0
- package/dist/__tests__/server/auth/apiKey.test.js +80 -0
- package/dist/__tests__/server/auth/token.test.d.ts +1 -0
- package/dist/__tests__/server/auth/token.test.js +212 -0
- package/dist/__tests__/server/http/responses.test.d.ts +1 -0
- package/dist/__tests__/server/http/responses.test.js +112 -0
- package/dist/__tests__/server/storage/client.test.d.ts +1 -0
- package/dist/__tests__/server/storage/client.test.js +173 -0
- package/dist/__tests__/server/storage/keys.test.d.ts +1 -0
- package/dist/__tests__/server/storage/keys.test.js +47 -0
- package/dist/__tests__/shared/auth/types.test.d.ts +1 -0
- package/dist/__tests__/shared/auth/types.test.js +77 -0
- package/dist/__tests__/shared/http/status.test.d.ts +1 -0
- package/dist/__tests__/shared/http/status.test.js +27 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +6 -5
- package/dist/server/auth/apiKey.d.ts +44 -0
- package/dist/server/auth/apiKey.js +59 -0
- package/dist/server/auth/index.d.ts +2 -0
- package/dist/server/auth/index.js +15 -0
- package/dist/server/auth/token.d.ts +56 -0
- package/dist/server/auth/token.js +104 -0
- package/dist/server/http/index.d.ts +1 -0
- package/dist/server/http/index.js +12 -0
- package/dist/server/http/responses.d.ts +82 -0
- package/dist/server/http/responses.js +132 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +33 -0
- package/dist/server/storage/client.d.ts +48 -0
- package/dist/server/storage/client.js +107 -0
- package/dist/server/storage/index.d.ts +2 -0
- package/dist/server/storage/index.js +11 -0
- package/dist/server/storage/keys.d.ts +8 -0
- package/dist/server/storage/keys.js +14 -0
- package/dist/shared/auth/index.d.ts +2 -0
- package/dist/shared/auth/index.js +7 -0
- package/dist/shared/auth/types.d.ts +63 -0
- package/dist/shared/auth/types.js +41 -0
- package/dist/shared/http/index.d.ts +3 -0
- package/dist/shared/http/index.js +5 -0
- package/dist/shared/http/status.d.ts +17 -0
- package/dist/shared/http/status.js +19 -0
- package/dist/shared/http/types.d.ts +10 -0
- package/dist/shared/http/types.js +5 -0
- package/dist/shared/index.d.ts +5 -0
- package/dist/shared/index.js +10 -0
- package/package.json +9 -13
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isConflictError = exports.isNotFoundError = exports.handleFunctionError = exports.conflictResponse = exports.notFoundResponse = exports.forbiddenResponse = exports.unauthorizedResponse = exports.badRequestResponse = void 0;
|
|
4
|
+
var responses_1 = require("./responses");
|
|
5
|
+
Object.defineProperty(exports, "badRequestResponse", { enumerable: true, get: function () { return responses_1.badRequestResponse; } });
|
|
6
|
+
Object.defineProperty(exports, "unauthorizedResponse", { enumerable: true, get: function () { return responses_1.unauthorizedResponse; } });
|
|
7
|
+
Object.defineProperty(exports, "forbiddenResponse", { enumerable: true, get: function () { return responses_1.forbiddenResponse; } });
|
|
8
|
+
Object.defineProperty(exports, "notFoundResponse", { enumerable: true, get: function () { return responses_1.notFoundResponse; } });
|
|
9
|
+
Object.defineProperty(exports, "conflictResponse", { enumerable: true, get: function () { return responses_1.conflictResponse; } });
|
|
10
|
+
Object.defineProperty(exports, "handleFunctionError", { enumerable: true, get: function () { return responses_1.handleFunctionError; } });
|
|
11
|
+
Object.defineProperty(exports, "isNotFoundError", { enumerable: true, get: function () { return responses_1.isNotFoundError; } });
|
|
12
|
+
Object.defineProperty(exports, "isConflictError", { enumerable: true, get: function () { return responses_1.isConflictError; } });
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a 400 Bad Request response.
|
|
4
|
+
* @param message - The error message to return
|
|
5
|
+
* @returns Azure Functions HttpResponseInit object
|
|
6
|
+
* @example
|
|
7
|
+
* if (!body.email) return badRequestResponse('Email is required');
|
|
8
|
+
*/
|
|
9
|
+
export declare function badRequestResponse(message: string): HttpResponseInit;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a 401 Unauthorized response.
|
|
12
|
+
* @param message - The error message (default: 'Unauthorized')
|
|
13
|
+
* @returns Azure Functions HttpResponseInit object
|
|
14
|
+
* @example
|
|
15
|
+
* if (!token) return unauthorizedResponse();
|
|
16
|
+
*/
|
|
17
|
+
export declare function unauthorizedResponse(message?: string): HttpResponseInit;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a 403 Forbidden response.
|
|
20
|
+
* @param message - The error message (default: 'Forbidden')
|
|
21
|
+
* @returns Azure Functions HttpResponseInit object
|
|
22
|
+
* @example
|
|
23
|
+
* if (!isAdmin(payload)) return forbiddenResponse('Admin access required');
|
|
24
|
+
*/
|
|
25
|
+
export declare function forbiddenResponse(message?: string): HttpResponseInit;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a 404 Not Found response.
|
|
28
|
+
* @param resource - The name of the resource that wasn't found
|
|
29
|
+
* @returns Azure Functions HttpResponseInit object
|
|
30
|
+
* @example
|
|
31
|
+
* if (!user) return notFoundResponse('User');
|
|
32
|
+
* // Returns: { error: 'User not found' }
|
|
33
|
+
*/
|
|
34
|
+
export declare function notFoundResponse(resource: string): HttpResponseInit;
|
|
35
|
+
/**
|
|
36
|
+
* Creates a 409 Conflict response.
|
|
37
|
+
* @param message - The conflict error message
|
|
38
|
+
* @returns Azure Functions HttpResponseInit object
|
|
39
|
+
* @example
|
|
40
|
+
* if (existingUser) return conflictResponse('Email already registered');
|
|
41
|
+
*/
|
|
42
|
+
export declare function conflictResponse(message: string): HttpResponseInit;
|
|
43
|
+
/**
|
|
44
|
+
* Handles unexpected errors safely by logging details and returning a generic message.
|
|
45
|
+
* Use in catch blocks to avoid exposing internal error details to clients.
|
|
46
|
+
* @param error - The caught error
|
|
47
|
+
* @param context - Azure Functions InvocationContext for logging
|
|
48
|
+
* @returns Azure Functions HttpResponseInit with 500 status
|
|
49
|
+
* @example
|
|
50
|
+
* try {
|
|
51
|
+
* await riskyOperation();
|
|
52
|
+
* } catch (error) {
|
|
53
|
+
* return handleFunctionError(error, context);
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
export declare function handleFunctionError(error: unknown, context: InvocationContext): HttpResponseInit;
|
|
57
|
+
/**
|
|
58
|
+
* Checks if an error is an Azure Table Storage "not found" error.
|
|
59
|
+
* @param error - The caught error
|
|
60
|
+
* @returns True if error has statusCode 404
|
|
61
|
+
* @example
|
|
62
|
+
* try {
|
|
63
|
+
* await tableClient.getEntity(pk, rk);
|
|
64
|
+
* } catch (error) {
|
|
65
|
+
* if (isNotFoundError(error)) return notFoundResponse('Entity');
|
|
66
|
+
* throw error;
|
|
67
|
+
* }
|
|
68
|
+
*/
|
|
69
|
+
export declare function isNotFoundError(error: unknown): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Checks if an error is an Azure Table Storage "conflict" error.
|
|
72
|
+
* @param error - The caught error
|
|
73
|
+
* @returns True if error has statusCode 409
|
|
74
|
+
* @example
|
|
75
|
+
* try {
|
|
76
|
+
* await tableClient.createEntity(entity);
|
|
77
|
+
* } catch (error) {
|
|
78
|
+
* if (isConflictError(error)) return conflictResponse('Entity already exists');
|
|
79
|
+
* throw error;
|
|
80
|
+
* }
|
|
81
|
+
*/
|
|
82
|
+
export declare function isConflictError(error: unknown): boolean;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.badRequestResponse = badRequestResponse;
|
|
4
|
+
exports.unauthorizedResponse = unauthorizedResponse;
|
|
5
|
+
exports.forbiddenResponse = forbiddenResponse;
|
|
6
|
+
exports.notFoundResponse = notFoundResponse;
|
|
7
|
+
exports.conflictResponse = conflictResponse;
|
|
8
|
+
exports.handleFunctionError = handleFunctionError;
|
|
9
|
+
exports.isNotFoundError = isNotFoundError;
|
|
10
|
+
exports.isConflictError = isConflictError;
|
|
11
|
+
const status_1 = require("../../shared/http/status");
|
|
12
|
+
/**
|
|
13
|
+
* Creates a 400 Bad Request response.
|
|
14
|
+
* @param message - The error message to return
|
|
15
|
+
* @returns Azure Functions HttpResponseInit object
|
|
16
|
+
* @example
|
|
17
|
+
* if (!body.email) return badRequestResponse('Email is required');
|
|
18
|
+
*/
|
|
19
|
+
function badRequestResponse(message) {
|
|
20
|
+
return {
|
|
21
|
+
status: status_1.HTTP_STATUS.BAD_REQUEST,
|
|
22
|
+
jsonBody: { error: message }
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a 401 Unauthorized response.
|
|
27
|
+
* @param message - The error message (default: 'Unauthorized')
|
|
28
|
+
* @returns Azure Functions HttpResponseInit object
|
|
29
|
+
* @example
|
|
30
|
+
* if (!token) return unauthorizedResponse();
|
|
31
|
+
*/
|
|
32
|
+
function unauthorizedResponse(message = 'Unauthorized') {
|
|
33
|
+
return {
|
|
34
|
+
status: status_1.HTTP_STATUS.UNAUTHORIZED,
|
|
35
|
+
jsonBody: { error: message }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a 403 Forbidden response.
|
|
40
|
+
* @param message - The error message (default: 'Forbidden')
|
|
41
|
+
* @returns Azure Functions HttpResponseInit object
|
|
42
|
+
* @example
|
|
43
|
+
* if (!isAdmin(payload)) return forbiddenResponse('Admin access required');
|
|
44
|
+
*/
|
|
45
|
+
function forbiddenResponse(message = 'Forbidden') {
|
|
46
|
+
return {
|
|
47
|
+
status: status_1.HTTP_STATUS.FORBIDDEN,
|
|
48
|
+
jsonBody: { error: message }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a 404 Not Found response.
|
|
53
|
+
* @param resource - The name of the resource that wasn't found
|
|
54
|
+
* @returns Azure Functions HttpResponseInit object
|
|
55
|
+
* @example
|
|
56
|
+
* if (!user) return notFoundResponse('User');
|
|
57
|
+
* // Returns: { error: 'User not found' }
|
|
58
|
+
*/
|
|
59
|
+
function notFoundResponse(resource) {
|
|
60
|
+
return {
|
|
61
|
+
status: status_1.HTTP_STATUS.NOT_FOUND,
|
|
62
|
+
jsonBody: { error: `${resource} not found` }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a 409 Conflict response.
|
|
67
|
+
* @param message - The conflict error message
|
|
68
|
+
* @returns Azure Functions HttpResponseInit object
|
|
69
|
+
* @example
|
|
70
|
+
* if (existingUser) return conflictResponse('Email already registered');
|
|
71
|
+
*/
|
|
72
|
+
function conflictResponse(message) {
|
|
73
|
+
return {
|
|
74
|
+
status: status_1.HTTP_STATUS.CONFLICT,
|
|
75
|
+
jsonBody: { error: message }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Handles unexpected errors safely by logging details and returning a generic message.
|
|
80
|
+
* Use in catch blocks to avoid exposing internal error details to clients.
|
|
81
|
+
* @param error - The caught error
|
|
82
|
+
* @param context - Azure Functions InvocationContext for logging
|
|
83
|
+
* @returns Azure Functions HttpResponseInit with 500 status
|
|
84
|
+
* @example
|
|
85
|
+
* try {
|
|
86
|
+
* await riskyOperation();
|
|
87
|
+
* } catch (error) {
|
|
88
|
+
* return handleFunctionError(error, context);
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
function handleFunctionError(error, context) {
|
|
92
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
93
|
+
context.error(`Function error: ${message}`);
|
|
94
|
+
return {
|
|
95
|
+
status: status_1.HTTP_STATUS.INTERNAL_ERROR,
|
|
96
|
+
jsonBody: { error: 'Internal server error' }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks if an error is an Azure Table Storage "not found" error.
|
|
101
|
+
* @param error - The caught error
|
|
102
|
+
* @returns True if error has statusCode 404
|
|
103
|
+
* @example
|
|
104
|
+
* try {
|
|
105
|
+
* await tableClient.getEntity(pk, rk);
|
|
106
|
+
* } catch (error) {
|
|
107
|
+
* if (isNotFoundError(error)) return notFoundResponse('Entity');
|
|
108
|
+
* throw error;
|
|
109
|
+
* }
|
|
110
|
+
*/
|
|
111
|
+
function isNotFoundError(error) {
|
|
112
|
+
return (error instanceof Error &&
|
|
113
|
+
'statusCode' in error &&
|
|
114
|
+
error.statusCode === 404);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Checks if an error is an Azure Table Storage "conflict" error.
|
|
118
|
+
* @param error - The caught error
|
|
119
|
+
* @returns True if error has statusCode 409
|
|
120
|
+
* @example
|
|
121
|
+
* try {
|
|
122
|
+
* await tableClient.createEntity(entity);
|
|
123
|
+
* } catch (error) {
|
|
124
|
+
* if (isConflictError(error)) return conflictResponse('Entity already exists');
|
|
125
|
+
* throw error;
|
|
126
|
+
* }
|
|
127
|
+
*/
|
|
128
|
+
function isConflictError(error) {
|
|
129
|
+
return (error instanceof Error &&
|
|
130
|
+
'statusCode' in error &&
|
|
131
|
+
error.statusCode === 409);
|
|
132
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { initAuth, getJwtSecret, extractToken, validateToken, generateToken, generateLongLivedToken, extractApiKey, hashApiKey, validateApiKey, generateApiKey } from './auth';
|
|
2
|
+
export { badRequestResponse, unauthorizedResponse, forbiddenResponse, notFoundResponse, conflictResponse, handleFunctionError, isNotFoundError, isConflictError } from './http';
|
|
3
|
+
export { initStorage, initStorageFromEnv, useManagedIdentity, getTableClient, clearTableClientCache, generateRowKey } from './storage';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRowKey = exports.clearTableClientCache = exports.getTableClient = exports.useManagedIdentity = exports.initStorageFromEnv = exports.initStorage = exports.isConflictError = exports.isNotFoundError = exports.handleFunctionError = exports.conflictResponse = exports.notFoundResponse = exports.forbiddenResponse = exports.unauthorizedResponse = exports.badRequestResponse = exports.generateApiKey = exports.validateApiKey = exports.hashApiKey = exports.extractApiKey = exports.generateLongLivedToken = exports.generateToken = exports.validateToken = exports.extractToken = exports.getJwtSecret = exports.initAuth = void 0;
|
|
4
|
+
// Auth
|
|
5
|
+
var auth_1 = require("./auth");
|
|
6
|
+
Object.defineProperty(exports, "initAuth", { enumerable: true, get: function () { return auth_1.initAuth; } });
|
|
7
|
+
Object.defineProperty(exports, "getJwtSecret", { enumerable: true, get: function () { return auth_1.getJwtSecret; } });
|
|
8
|
+
Object.defineProperty(exports, "extractToken", { enumerable: true, get: function () { return auth_1.extractToken; } });
|
|
9
|
+
Object.defineProperty(exports, "validateToken", { enumerable: true, get: function () { return auth_1.validateToken; } });
|
|
10
|
+
Object.defineProperty(exports, "generateToken", { enumerable: true, get: function () { return auth_1.generateToken; } });
|
|
11
|
+
Object.defineProperty(exports, "generateLongLivedToken", { enumerable: true, get: function () { return auth_1.generateLongLivedToken; } });
|
|
12
|
+
Object.defineProperty(exports, "extractApiKey", { enumerable: true, get: function () { return auth_1.extractApiKey; } });
|
|
13
|
+
Object.defineProperty(exports, "hashApiKey", { enumerable: true, get: function () { return auth_1.hashApiKey; } });
|
|
14
|
+
Object.defineProperty(exports, "validateApiKey", { enumerable: true, get: function () { return auth_1.validateApiKey; } });
|
|
15
|
+
Object.defineProperty(exports, "generateApiKey", { enumerable: true, get: function () { return auth_1.generateApiKey; } });
|
|
16
|
+
// HTTP
|
|
17
|
+
var http_1 = require("./http");
|
|
18
|
+
Object.defineProperty(exports, "badRequestResponse", { enumerable: true, get: function () { return http_1.badRequestResponse; } });
|
|
19
|
+
Object.defineProperty(exports, "unauthorizedResponse", { enumerable: true, get: function () { return http_1.unauthorizedResponse; } });
|
|
20
|
+
Object.defineProperty(exports, "forbiddenResponse", { enumerable: true, get: function () { return http_1.forbiddenResponse; } });
|
|
21
|
+
Object.defineProperty(exports, "notFoundResponse", { enumerable: true, get: function () { return http_1.notFoundResponse; } });
|
|
22
|
+
Object.defineProperty(exports, "conflictResponse", { enumerable: true, get: function () { return http_1.conflictResponse; } });
|
|
23
|
+
Object.defineProperty(exports, "handleFunctionError", { enumerable: true, get: function () { return http_1.handleFunctionError; } });
|
|
24
|
+
Object.defineProperty(exports, "isNotFoundError", { enumerable: true, get: function () { return http_1.isNotFoundError; } });
|
|
25
|
+
Object.defineProperty(exports, "isConflictError", { enumerable: true, get: function () { return http_1.isConflictError; } });
|
|
26
|
+
// Storage
|
|
27
|
+
var storage_1 = require("./storage");
|
|
28
|
+
Object.defineProperty(exports, "initStorage", { enumerable: true, get: function () { return storage_1.initStorage; } });
|
|
29
|
+
Object.defineProperty(exports, "initStorageFromEnv", { enumerable: true, get: function () { return storage_1.initStorageFromEnv; } });
|
|
30
|
+
Object.defineProperty(exports, "useManagedIdentity", { enumerable: true, get: function () { return storage_1.useManagedIdentity; } });
|
|
31
|
+
Object.defineProperty(exports, "getTableClient", { enumerable: true, get: function () { return storage_1.getTableClient; } });
|
|
32
|
+
Object.defineProperty(exports, "clearTableClientCache", { enumerable: true, get: function () { return storage_1.clearTableClientCache; } });
|
|
33
|
+
Object.defineProperty(exports, "generateRowKey", { enumerable: true, get: function () { return storage_1.generateRowKey; } });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TableClient } from '@azure/data-tables';
|
|
2
|
+
/**
|
|
3
|
+
* Initializes Azure Table Storage configuration. Call once at application startup.
|
|
4
|
+
* @param config - Storage configuration options
|
|
5
|
+
* @param config.accountName - Azure Storage account name (for managed identity)
|
|
6
|
+
* @param config.connectionString - Connection string (for local development)
|
|
7
|
+
* @example
|
|
8
|
+
* // Production (Azure with managed identity)
|
|
9
|
+
* initStorage({ accountName: 'mystorageaccount' });
|
|
10
|
+
*
|
|
11
|
+
* // Local development (Azurite)
|
|
12
|
+
* initStorage({ connectionString: 'UseDevelopmentStorage=true' });
|
|
13
|
+
*/
|
|
14
|
+
export declare function initStorage(config: {
|
|
15
|
+
accountName?: string;
|
|
16
|
+
connectionString?: string;
|
|
17
|
+
}): void;
|
|
18
|
+
/**
|
|
19
|
+
* Initializes storage from environment variables.
|
|
20
|
+
* Reads STORAGE_ACCOUNT_NAME and AzureWebJobsStorage.
|
|
21
|
+
* @example
|
|
22
|
+
* initStorageFromEnv(); // Uses process.env automatically
|
|
23
|
+
*/
|
|
24
|
+
export declare function initStorageFromEnv(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Checks if using Azure managed identity vs local connection string.
|
|
27
|
+
* @returns True if using managed identity (production), false for connection string (local)
|
|
28
|
+
*/
|
|
29
|
+
export declare function useManagedIdentity(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Gets a TableClient for the specified table, creating the table if needed.
|
|
32
|
+
* Clients are cached for reuse across requests.
|
|
33
|
+
* @param tableName - The Azure Table Storage table name
|
|
34
|
+
* @returns A configured TableClient instance
|
|
35
|
+
* @throws Error if storage is not configured
|
|
36
|
+
* @example
|
|
37
|
+
* const client = await getTableClient('users');
|
|
38
|
+
* await client.createEntity({ partitionKey: 'pk', rowKey: 'rk', name: 'John' });
|
|
39
|
+
*/
|
|
40
|
+
export declare function getTableClient(tableName: string): Promise<TableClient>;
|
|
41
|
+
/**
|
|
42
|
+
* Clears the TableClient cache. Primarily useful for testing.
|
|
43
|
+
* @example
|
|
44
|
+
* afterEach(() => {
|
|
45
|
+
* clearTableClientCache();
|
|
46
|
+
* });
|
|
47
|
+
*/
|
|
48
|
+
export declare function clearTableClientCache(): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initStorage = initStorage;
|
|
4
|
+
exports.initStorageFromEnv = initStorageFromEnv;
|
|
5
|
+
exports.useManagedIdentity = useManagedIdentity;
|
|
6
|
+
exports.getTableClient = getTableClient;
|
|
7
|
+
exports.clearTableClientCache = clearTableClientCache;
|
|
8
|
+
const data_tables_1 = require("@azure/data-tables");
|
|
9
|
+
const identity_1 = require("@azure/identity");
|
|
10
|
+
/**
|
|
11
|
+
* Azure Table Storage client factory with managed identity support
|
|
12
|
+
*/
|
|
13
|
+
// Cache table clients to avoid repeated connections
|
|
14
|
+
const tableClients = new Map();
|
|
15
|
+
// Configuration
|
|
16
|
+
let storageAccountName;
|
|
17
|
+
let connectionString;
|
|
18
|
+
/**
|
|
19
|
+
* Initializes Azure Table Storage configuration. Call once at application startup.
|
|
20
|
+
* @param config - Storage configuration options
|
|
21
|
+
* @param config.accountName - Azure Storage account name (for managed identity)
|
|
22
|
+
* @param config.connectionString - Connection string (for local development)
|
|
23
|
+
* @example
|
|
24
|
+
* // Production (Azure with managed identity)
|
|
25
|
+
* initStorage({ accountName: 'mystorageaccount' });
|
|
26
|
+
*
|
|
27
|
+
* // Local development (Azurite)
|
|
28
|
+
* initStorage({ connectionString: 'UseDevelopmentStorage=true' });
|
|
29
|
+
*/
|
|
30
|
+
function initStorage(config) {
|
|
31
|
+
storageAccountName = config.accountName;
|
|
32
|
+
connectionString = config.connectionString;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Initializes storage from environment variables.
|
|
36
|
+
* Reads STORAGE_ACCOUNT_NAME and AzureWebJobsStorage.
|
|
37
|
+
* @example
|
|
38
|
+
* initStorageFromEnv(); // Uses process.env automatically
|
|
39
|
+
*/
|
|
40
|
+
function initStorageFromEnv() {
|
|
41
|
+
initStorage({
|
|
42
|
+
accountName: process.env.STORAGE_ACCOUNT_NAME,
|
|
43
|
+
connectionString: process.env.AzureWebJobsStorage
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Checks if using Azure managed identity vs local connection string.
|
|
48
|
+
* @returns True if using managed identity (production), false for connection string (local)
|
|
49
|
+
*/
|
|
50
|
+
function useManagedIdentity() {
|
|
51
|
+
return !!storageAccountName && !connectionString?.includes('UseDevelopmentStorage');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Gets a TableClient for the specified table, creating the table if needed.
|
|
55
|
+
* Clients are cached for reuse across requests.
|
|
56
|
+
* @param tableName - The Azure Table Storage table name
|
|
57
|
+
* @returns A configured TableClient instance
|
|
58
|
+
* @throws Error if storage is not configured
|
|
59
|
+
* @example
|
|
60
|
+
* const client = await getTableClient('users');
|
|
61
|
+
* await client.createEntity({ partitionKey: 'pk', rowKey: 'rk', name: 'John' });
|
|
62
|
+
*/
|
|
63
|
+
async function getTableClient(tableName) {
|
|
64
|
+
// Return cached client if available
|
|
65
|
+
const cached = tableClients.get(tableName);
|
|
66
|
+
if (cached) {
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
69
|
+
let client;
|
|
70
|
+
if (useManagedIdentity()) {
|
|
71
|
+
// Azure: Use managed identity
|
|
72
|
+
const credential = new identity_1.DefaultAzureCredential();
|
|
73
|
+
const endpoint = `https://${storageAccountName}.table.core.windows.net`;
|
|
74
|
+
client = new data_tables_1.TableClient(endpoint, tableName, credential);
|
|
75
|
+
}
|
|
76
|
+
else if (connectionString) {
|
|
77
|
+
// Local/Azurite: Use connection string
|
|
78
|
+
client = data_tables_1.TableClient.fromConnectionString(connectionString, tableName);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error('Storage not configured. Set STORAGE_ACCOUNT_NAME or AzureWebJobsStorage.');
|
|
82
|
+
}
|
|
83
|
+
// Create table if it doesn't exist
|
|
84
|
+
try {
|
|
85
|
+
await client.createTable();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
// Ignore "table already exists" errors (409 Conflict)
|
|
89
|
+
const tableError = error;
|
|
90
|
+
if (tableError.statusCode !== 409) {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Cache and return
|
|
95
|
+
tableClients.set(tableName, client);
|
|
96
|
+
return client;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clears the TableClient cache. Primarily useful for testing.
|
|
100
|
+
* @example
|
|
101
|
+
* afterEach(() => {
|
|
102
|
+
* clearTableClientCache();
|
|
103
|
+
* });
|
|
104
|
+
*/
|
|
105
|
+
function clearTableClientCache() {
|
|
106
|
+
tableClients.clear();
|
|
107
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRowKey = exports.clearTableClientCache = exports.getTableClient = exports.useManagedIdentity = exports.initStorageFromEnv = exports.initStorage = void 0;
|
|
4
|
+
var client_1 = require("./client");
|
|
5
|
+
Object.defineProperty(exports, "initStorage", { enumerable: true, get: function () { return client_1.initStorage; } });
|
|
6
|
+
Object.defineProperty(exports, "initStorageFromEnv", { enumerable: true, get: function () { return client_1.initStorageFromEnv; } });
|
|
7
|
+
Object.defineProperty(exports, "useManagedIdentity", { enumerable: true, get: function () { return client_1.useManagedIdentity; } });
|
|
8
|
+
Object.defineProperty(exports, "getTableClient", { enumerable: true, get: function () { return client_1.getTableClient; } });
|
|
9
|
+
Object.defineProperty(exports, "clearTableClientCache", { enumerable: true, get: function () { return client_1.clearTableClientCache; } });
|
|
10
|
+
var keys_1 = require("./keys");
|
|
11
|
+
Object.defineProperty(exports, "generateRowKey", { enumerable: true, get: function () { return keys_1.generateRowKey; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a unique row key from an identifier string
|
|
3
|
+
* Uses SHA-256 hash for consistent, URL-safe keys
|
|
4
|
+
*
|
|
5
|
+
* @param identifier - The string to hash (e.g., subscription endpoint, user ID)
|
|
6
|
+
* @returns A 32-character hex string suitable for Azure Table Storage row keys
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateRowKey(identifier: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRowKey = generateRowKey;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
/**
|
|
6
|
+
* Generate a unique row key from an identifier string
|
|
7
|
+
* Uses SHA-256 hash for consistent, URL-safe keys
|
|
8
|
+
*
|
|
9
|
+
* @param identifier - The string to hash (e.g., subscription endpoint, user ID)
|
|
10
|
+
* @returns A 32-character hex string suitable for Azure Table Storage row keys
|
|
11
|
+
*/
|
|
12
|
+
function generateRowKey(identifier) {
|
|
13
|
+
return (0, crypto_1.createHash)('sha256').update(identifier).digest('hex').substring(0, 32);
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAdmin = exports.hasRole = exports.hasUsername = void 0;
|
|
4
|
+
var types_1 = require("./types");
|
|
5
|
+
Object.defineProperty(exports, "hasUsername", { enumerable: true, get: function () { return types_1.hasUsername; } });
|
|
6
|
+
Object.defineProperty(exports, "hasRole", { enumerable: true, get: function () { return types_1.hasRole; } });
|
|
7
|
+
Object.defineProperty(exports, "isAdmin", { enumerable: true, get: function () { return types_1.isAdmin; } });
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base JWT payload - all tokens include these fields
|
|
3
|
+
* Projects extend this with their specific fields
|
|
4
|
+
*/
|
|
5
|
+
export interface BaseJwtPayload {
|
|
6
|
+
iat: number;
|
|
7
|
+
exp: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Standard user token payload
|
|
11
|
+
* Used by: azure-pwa-starter, azure-alert-service (admin), onsite-monitor
|
|
12
|
+
*/
|
|
13
|
+
export interface UserTokenPayload extends BaseJwtPayload {
|
|
14
|
+
authenticated: true;
|
|
15
|
+
tokenType: 'user' | 'machine';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Username-based token payload
|
|
19
|
+
* Used by: financial-tracker
|
|
20
|
+
*/
|
|
21
|
+
export interface UsernameTokenPayload extends BaseJwtPayload {
|
|
22
|
+
username: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Role-based token payload
|
|
26
|
+
* Used by: azure-alert-service
|
|
27
|
+
*/
|
|
28
|
+
export interface RoleTokenPayload extends BaseJwtPayload {
|
|
29
|
+
authenticated: true;
|
|
30
|
+
tokenType: 'user' | 'machine';
|
|
31
|
+
role: 'admin' | 'viewer';
|
|
32
|
+
viewerTokenId?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Type guard to check if a JWT payload contains a username field.
|
|
36
|
+
* @param payload - The JWT payload to check
|
|
37
|
+
* @returns True if payload has a string username field
|
|
38
|
+
* @example
|
|
39
|
+
* if (hasUsername(payload)) {
|
|
40
|
+
* console.log(payload.username); // TypeScript knows username exists
|
|
41
|
+
* }
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasUsername(payload: BaseJwtPayload): payload is UsernameTokenPayload;
|
|
44
|
+
/**
|
|
45
|
+
* Type guard to check if a JWT payload contains a role field.
|
|
46
|
+
* @param payload - The JWT payload to check
|
|
47
|
+
* @returns True if payload has a role field
|
|
48
|
+
* @example
|
|
49
|
+
* if (hasRole(payload)) {
|
|
50
|
+
* console.log(payload.role); // 'admin' | 'viewer'
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
export declare function hasRole(payload: BaseJwtPayload): payload is RoleTokenPayload;
|
|
54
|
+
/**
|
|
55
|
+
* Checks if a JWT payload represents an admin user.
|
|
56
|
+
* @param payload - The JWT payload to check
|
|
57
|
+
* @returns True if payload has role='admin'
|
|
58
|
+
* @example
|
|
59
|
+
* if (!isAdmin(payload)) {
|
|
60
|
+
* return httpForbidden('Admin access required');
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
export declare function isAdmin(payload: BaseJwtPayload): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasUsername = hasUsername;
|
|
4
|
+
exports.hasRole = hasRole;
|
|
5
|
+
exports.isAdmin = isAdmin;
|
|
6
|
+
/**
|
|
7
|
+
* Type guard to check if a JWT payload contains a username field.
|
|
8
|
+
* @param payload - The JWT payload to check
|
|
9
|
+
* @returns True if payload has a string username field
|
|
10
|
+
* @example
|
|
11
|
+
* if (hasUsername(payload)) {
|
|
12
|
+
* console.log(payload.username); // TypeScript knows username exists
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
function hasUsername(payload) {
|
|
16
|
+
return 'username' in payload && typeof payload.username === 'string';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Type guard to check if a JWT payload contains a role field.
|
|
20
|
+
* @param payload - The JWT payload to check
|
|
21
|
+
* @returns True if payload has a role field
|
|
22
|
+
* @example
|
|
23
|
+
* if (hasRole(payload)) {
|
|
24
|
+
* console.log(payload.role); // 'admin' | 'viewer'
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
function hasRole(payload) {
|
|
28
|
+
return 'role' in payload;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a JWT payload represents an admin user.
|
|
32
|
+
* @param payload - The JWT payload to check
|
|
33
|
+
* @returns True if payload has role='admin'
|
|
34
|
+
* @example
|
|
35
|
+
* if (!isAdmin(payload)) {
|
|
36
|
+
* return httpForbidden('Admin access required');
|
|
37
|
+
* }
|
|
38
|
+
*/
|
|
39
|
+
function isAdmin(payload) {
|
|
40
|
+
return hasRole(payload) && payload.role === 'admin';
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP status codes - use instead of magic numbers
|
|
3
|
+
*/
|
|
4
|
+
export declare const HTTP_STATUS: {
|
|
5
|
+
readonly OK: 200;
|
|
6
|
+
readonly CREATED: 201;
|
|
7
|
+
readonly NO_CONTENT: 204;
|
|
8
|
+
readonly BAD_REQUEST: 400;
|
|
9
|
+
readonly UNAUTHORIZED: 401;
|
|
10
|
+
readonly FORBIDDEN: 403;
|
|
11
|
+
readonly NOT_FOUND: 404;
|
|
12
|
+
readonly CONFLICT: 409;
|
|
13
|
+
readonly GONE: 410;
|
|
14
|
+
readonly INTERNAL_ERROR: 500;
|
|
15
|
+
readonly SERVICE_UNAVAILABLE: 503;
|
|
16
|
+
};
|
|
17
|
+
export type HttpStatus = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HTTP_STATUS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* HTTP status codes - use instead of magic numbers
|
|
6
|
+
*/
|
|
7
|
+
exports.HTTP_STATUS = {
|
|
8
|
+
OK: 200,
|
|
9
|
+
CREATED: 201,
|
|
10
|
+
NO_CONTENT: 204,
|
|
11
|
+
BAD_REQUEST: 400,
|
|
12
|
+
UNAUTHORIZED: 401,
|
|
13
|
+
FORBIDDEN: 403,
|
|
14
|
+
NOT_FOUND: 404,
|
|
15
|
+
CONFLICT: 409,
|
|
16
|
+
GONE: 410,
|
|
17
|
+
INTERNAL_ERROR: 500,
|
|
18
|
+
SERVICE_UNAVAILABLE: 503
|
|
19
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { BaseJwtPayload, UserTokenPayload, UsernameTokenPayload, RoleTokenPayload } from './auth';
|
|
2
|
+
export { hasUsername, hasRole, isAdmin } from './auth';
|
|
3
|
+
export { HTTP_STATUS } from './http';
|
|
4
|
+
export type { HttpStatus } from './http';
|
|
5
|
+
export type { ErrorResponse } from './http';
|