@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,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
// Create mock functions
|
|
38
|
+
const mockCreateTable = vitest_1.vi.fn();
|
|
39
|
+
const mockFromConnectionString = vitest_1.vi.fn();
|
|
40
|
+
// Mock @azure/data-tables
|
|
41
|
+
vitest_1.vi.mock('@azure/data-tables', () => {
|
|
42
|
+
class MockTableClient {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.createTable = mockCreateTable;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
MockTableClient.fromConnectionString = mockFromConnectionString;
|
|
48
|
+
return {
|
|
49
|
+
TableClient: MockTableClient,
|
|
50
|
+
TableServiceClient: class MockTableServiceClient {
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
// Mock @azure/identity
|
|
55
|
+
vitest_1.vi.mock('@azure/identity', () => ({
|
|
56
|
+
DefaultAzureCredential: class MockDefaultAzureCredential {
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
59
|
+
(0, vitest_1.describe)('Storage client', () => {
|
|
60
|
+
(0, vitest_1.beforeEach)(() => {
|
|
61
|
+
vitest_1.vi.resetModules();
|
|
62
|
+
vitest_1.vi.clearAllMocks();
|
|
63
|
+
mockCreateTable.mockResolvedValue(undefined);
|
|
64
|
+
mockFromConnectionString.mockReturnValue({
|
|
65
|
+
createTable: vitest_1.vi.fn().mockResolvedValue(undefined)
|
|
66
|
+
});
|
|
67
|
+
// Reset environment
|
|
68
|
+
delete process.env.STORAGE_ACCOUNT_NAME;
|
|
69
|
+
delete process.env.AzureWebJobsStorage;
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.describe)('initStorage', () => {
|
|
72
|
+
(0, vitest_1.it)('accepts configuration object', async () => {
|
|
73
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
74
|
+
initStorage({ accountName: 'myaccount' });
|
|
75
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.it)('accepts connection string', async () => {
|
|
78
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
79
|
+
initStorage({ connectionString: 'DefaultEndpointsProtocol=https;AccountName=test' });
|
|
80
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.describe)('initStorageFromEnv', () => {
|
|
84
|
+
(0, vitest_1.it)('reads STORAGE_ACCOUNT_NAME', async () => {
|
|
85
|
+
process.env.STORAGE_ACCOUNT_NAME = 'testaccount';
|
|
86
|
+
const { initStorageFromEnv, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
87
|
+
initStorageFromEnv();
|
|
88
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)('reads AzureWebJobsStorage', async () => {
|
|
91
|
+
process.env.AzureWebJobsStorage = 'UseDevelopmentStorage=true';
|
|
92
|
+
const { initStorageFromEnv, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
93
|
+
initStorageFromEnv();
|
|
94
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.describe)('useManagedIdentity', () => {
|
|
98
|
+
(0, vitest_1.it)('returns true when accountName is set without development storage', async () => {
|
|
99
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
100
|
+
initStorage({ accountName: 'prodaccount' });
|
|
101
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.it)('returns false when using development storage', async () => {
|
|
104
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
105
|
+
initStorage({
|
|
106
|
+
accountName: 'devaccount',
|
|
107
|
+
connectionString: 'UseDevelopmentStorage=true'
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.it)('returns false when only connection string is set', async () => {
|
|
112
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
113
|
+
initStorage({ connectionString: 'DefaultEndpointsProtocol=https' });
|
|
114
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
(0, vitest_1.describe)('getTableClient', () => {
|
|
118
|
+
(0, vitest_1.it)('throws if storage not configured', async () => {
|
|
119
|
+
const { getTableClient } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
120
|
+
await (0, vitest_1.expect)(getTableClient('test')).rejects.toThrow('Storage not configured. Set STORAGE_ACCOUNT_NAME or AzureWebJobsStorage.');
|
|
121
|
+
});
|
|
122
|
+
(0, vitest_1.it)('creates table client with managed identity', async () => {
|
|
123
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
124
|
+
initStorage({ accountName: 'testaccount' });
|
|
125
|
+
clearTableClientCache();
|
|
126
|
+
const client = await getTableClient('mytable');
|
|
127
|
+
(0, vitest_1.expect)(client).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
(0, vitest_1.it)('creates table client with connection string', async () => {
|
|
130
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
131
|
+
initStorage({ connectionString: 'DefaultEndpointsProtocol=https;AccountName=test' });
|
|
132
|
+
clearTableClientCache();
|
|
133
|
+
await getTableClient('mytable');
|
|
134
|
+
(0, vitest_1.expect)(mockFromConnectionString).toHaveBeenCalledWith('DefaultEndpointsProtocol=https;AccountName=test', 'mytable');
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.it)('caches table client', async () => {
|
|
137
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
138
|
+
initStorage({ accountName: 'testaccount' });
|
|
139
|
+
clearTableClientCache();
|
|
140
|
+
const client1 = await getTableClient('mytable');
|
|
141
|
+
const client2 = await getTableClient('mytable');
|
|
142
|
+
(0, vitest_1.expect)(client1).toBe(client2);
|
|
143
|
+
});
|
|
144
|
+
(0, vitest_1.it)('handles 409 conflict (table exists)', async () => {
|
|
145
|
+
mockCreateTable.mockRejectedValue({ statusCode: 409 });
|
|
146
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
147
|
+
initStorage({ accountName: 'testaccount' });
|
|
148
|
+
clearTableClientCache();
|
|
149
|
+
// Should not throw
|
|
150
|
+
const client = await getTableClient('existingtable');
|
|
151
|
+
(0, vitest_1.expect)(client).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('throws on other errors', async () => {
|
|
154
|
+
mockCreateTable.mockRejectedValue({ statusCode: 500, message: 'Server error' });
|
|
155
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
156
|
+
initStorage({ accountName: 'testaccount' });
|
|
157
|
+
clearTableClientCache();
|
|
158
|
+
await (0, vitest_1.expect)(getTableClient('mytable')).rejects.toMatchObject({ statusCode: 500 });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
(0, vitest_1.describe)('clearTableClientCache', () => {
|
|
162
|
+
(0, vitest_1.it)('clears cached clients', async () => {
|
|
163
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../../server/storage/client')));
|
|
164
|
+
initStorage({ accountName: 'testaccount' });
|
|
165
|
+
clearTableClientCache();
|
|
166
|
+
await getTableClient('mytable');
|
|
167
|
+
clearTableClientCache();
|
|
168
|
+
// After clearing, a new call should create a new client
|
|
169
|
+
const client2 = await getTableClient('mytable');
|
|
170
|
+
(0, vitest_1.expect)(client2).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const keys_1 = require("../../../server/storage/keys");
|
|
5
|
+
(0, vitest_1.describe)('generateRowKey', () => {
|
|
6
|
+
(0, vitest_1.it)('returns 32-character hex string', () => {
|
|
7
|
+
const key = (0, keys_1.generateRowKey)('test-identifier');
|
|
8
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
9
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
10
|
+
});
|
|
11
|
+
(0, vitest_1.it)('produces consistent hash for same input', () => {
|
|
12
|
+
const key1 = (0, keys_1.generateRowKey)('same-input');
|
|
13
|
+
const key2 = (0, keys_1.generateRowKey)('same-input');
|
|
14
|
+
(0, vitest_1.expect)(key1).toBe(key2);
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)('produces different hashes for different inputs', () => {
|
|
17
|
+
const key1 = (0, keys_1.generateRowKey)('input-1');
|
|
18
|
+
const key2 = (0, keys_1.generateRowKey)('input-2');
|
|
19
|
+
(0, vitest_1.expect)(key1).not.toBe(key2);
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)('handles empty string', () => {
|
|
22
|
+
const key = (0, keys_1.generateRowKey)('');
|
|
23
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
24
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)('handles special characters', () => {
|
|
27
|
+
const key = (0, keys_1.generateRowKey)('https://example.com/path?query=value&foo=bar');
|
|
28
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
29
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)('handles unicode characters', () => {
|
|
32
|
+
const key = (0, keys_1.generateRowKey)('日本語テスト');
|
|
33
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
34
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)('handles long strings', () => {
|
|
37
|
+
const longString = 'x'.repeat(10000);
|
|
38
|
+
const key = (0, keys_1.generateRowKey)(longString);
|
|
39
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
40
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)('produces URL-safe keys', () => {
|
|
43
|
+
const key = (0, keys_1.generateRowKey)('some-identifier');
|
|
44
|
+
// Row keys should not contain these characters
|
|
45
|
+
(0, vitest_1.expect)(key).not.toMatch(/[\/\\#?\s]/);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const types_1 = require("../../../shared/auth/types");
|
|
5
|
+
(0, vitest_1.describe)('Auth type guards', () => {
|
|
6
|
+
const basePayload = {
|
|
7
|
+
iat: 1234567890,
|
|
8
|
+
exp: 1234567890 + 3600
|
|
9
|
+
};
|
|
10
|
+
(0, vitest_1.describe)('hasUsername', () => {
|
|
11
|
+
(0, vitest_1.it)('returns true for payload with username', () => {
|
|
12
|
+
const payload = { ...basePayload, username: 'testuser' };
|
|
13
|
+
(0, vitest_1.expect)((0, types_1.hasUsername)(payload)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('returns false for payload without username', () => {
|
|
16
|
+
(0, vitest_1.expect)((0, types_1.hasUsername)(basePayload)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.it)('returns false for payload with non-string username', () => {
|
|
19
|
+
const payload = { ...basePayload, username: 123 };
|
|
20
|
+
(0, vitest_1.expect)((0, types_1.hasUsername)(payload)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)('returns false for payload with empty string username', () => {
|
|
23
|
+
const payload = { ...basePayload, username: '' };
|
|
24
|
+
(0, vitest_1.expect)((0, types_1.hasUsername)(payload)).toBe(true); // Empty string is still a string
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.describe)('hasRole', () => {
|
|
28
|
+
(0, vitest_1.it)('returns true for payload with role', () => {
|
|
29
|
+
const payload = {
|
|
30
|
+
...basePayload,
|
|
31
|
+
authenticated: true,
|
|
32
|
+
tokenType: 'user',
|
|
33
|
+
role: 'admin'
|
|
34
|
+
};
|
|
35
|
+
(0, vitest_1.expect)((0, types_1.hasRole)(payload)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)('returns false for payload without role', () => {
|
|
38
|
+
(0, vitest_1.expect)((0, types_1.hasRole)(basePayload)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)('returns true for viewer role', () => {
|
|
41
|
+
const payload = {
|
|
42
|
+
...basePayload,
|
|
43
|
+
authenticated: true,
|
|
44
|
+
tokenType: 'user',
|
|
45
|
+
role: 'viewer'
|
|
46
|
+
};
|
|
47
|
+
(0, vitest_1.expect)((0, types_1.hasRole)(payload)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.describe)('isAdmin', () => {
|
|
51
|
+
(0, vitest_1.it)('returns true for admin role', () => {
|
|
52
|
+
const payload = {
|
|
53
|
+
...basePayload,
|
|
54
|
+
authenticated: true,
|
|
55
|
+
tokenType: 'user',
|
|
56
|
+
role: 'admin'
|
|
57
|
+
};
|
|
58
|
+
(0, vitest_1.expect)((0, types_1.isAdmin)(payload)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)('returns false for viewer role', () => {
|
|
61
|
+
const payload = {
|
|
62
|
+
...basePayload,
|
|
63
|
+
authenticated: true,
|
|
64
|
+
tokenType: 'user',
|
|
65
|
+
role: 'viewer'
|
|
66
|
+
};
|
|
67
|
+
(0, vitest_1.expect)((0, types_1.isAdmin)(payload)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.it)('returns false for payload without role', () => {
|
|
70
|
+
(0, vitest_1.expect)((0, types_1.isAdmin)(basePayload)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)('returns false for payload with username but no role', () => {
|
|
73
|
+
const payload = { ...basePayload, username: 'testuser' };
|
|
74
|
+
(0, vitest_1.expect)((0, types_1.isAdmin)(payload)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const status_1 = require("../../../shared/http/status");
|
|
5
|
+
(0, vitest_1.describe)('HTTP_STATUS', () => {
|
|
6
|
+
(0, vitest_1.it)('has correct status codes', () => {
|
|
7
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.OK).toBe(200);
|
|
8
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.CREATED).toBe(201);
|
|
9
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.NO_CONTENT).toBe(204);
|
|
10
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.BAD_REQUEST).toBe(400);
|
|
11
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.UNAUTHORIZED).toBe(401);
|
|
12
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.FORBIDDEN).toBe(403);
|
|
13
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.NOT_FOUND).toBe(404);
|
|
14
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.CONFLICT).toBe(409);
|
|
15
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.GONE).toBe(410);
|
|
16
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.INTERNAL_ERROR).toBe(500);
|
|
17
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.SERVICE_UNAVAILABLE).toBe(503);
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('is immutable (const assertion)', () => {
|
|
20
|
+
// TypeScript const assertion makes the object readonly
|
|
21
|
+
// This test verifies the values haven't been accidentally modified
|
|
22
|
+
const statusCodes = Object.values(status_1.HTTP_STATUS);
|
|
23
|
+
(0, vitest_1.expect)(statusCodes).toContain(200);
|
|
24
|
+
(0, vitest_1.expect)(statusCodes).toContain(404);
|
|
25
|
+
(0, vitest_1.expect)(statusCodes).toContain(500);
|
|
26
|
+
});
|
|
27
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,9 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
//
|
|
17
|
+
// Core types (universal)
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
|
-
|
|
20
|
-
__exportStar(require("./
|
|
21
|
-
|
|
22
|
-
__exportStar(require("./
|
|
19
|
+
// Server utilities (for convenience, but prefer ./server)
|
|
20
|
+
__exportStar(require("./server"), exports);
|
|
21
|
+
// Shared utilities
|
|
22
|
+
__exportStar(require("./shared"), exports);
|
|
23
|
+
// Note: Client utilities available via ./client subpath
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Result } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* API Key utilities for machine-to-machine authentication
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Extracts API key from the X-API-Key header.
|
|
7
|
+
* @param request - Request object with headers.get() method
|
|
8
|
+
* @returns The API key string, or null if not present
|
|
9
|
+
* @example
|
|
10
|
+
* const apiKey = extractApiKey(request);
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractApiKey(request: {
|
|
13
|
+
headers: {
|
|
14
|
+
get(name: string): string | null;
|
|
15
|
+
};
|
|
16
|
+
}): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Hashes an API key using SHA-256 for secure storage.
|
|
19
|
+
* Store this hash in your database, never the raw key.
|
|
20
|
+
* @param apiKey - The raw API key to hash
|
|
21
|
+
* @returns The SHA-256 hash as a hex string
|
|
22
|
+
* @example
|
|
23
|
+
* const hash = hashApiKey(rawKey);
|
|
24
|
+
* await db.save({ apiKeyHash: hash });
|
|
25
|
+
*/
|
|
26
|
+
export declare function hashApiKey(apiKey: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Validates an API key against a stored hash.
|
|
29
|
+
* @param apiKey - The API key from the request
|
|
30
|
+
* @param storedHash - The hash stored in your database
|
|
31
|
+
* @returns Result with ok=true if valid, or error message if invalid
|
|
32
|
+
* @example
|
|
33
|
+
* const result = validateApiKey(apiKey, user.apiKeyHash);
|
|
34
|
+
* if (!result.ok) return httpUnauthorized();
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateApiKey(apiKey: string, storedHash: string): Result<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Generates a cryptographically secure API key.
|
|
39
|
+
* @returns A random 64-character hex string (32 bytes)
|
|
40
|
+
* @example
|
|
41
|
+
* const apiKey = generateApiKey();
|
|
42
|
+
* // Return to user once, store hash in database
|
|
43
|
+
*/
|
|
44
|
+
export declare function generateApiKey(): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractApiKey = extractApiKey;
|
|
4
|
+
exports.hashApiKey = hashApiKey;
|
|
5
|
+
exports.validateApiKey = validateApiKey;
|
|
6
|
+
exports.generateApiKey = generateApiKey;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const types_1 = require("../../types");
|
|
9
|
+
/**
|
|
10
|
+
* API Key utilities for machine-to-machine authentication
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Extracts API key from the X-API-Key header.
|
|
14
|
+
* @param request - Request object with headers.get() method
|
|
15
|
+
* @returns The API key string, or null if not present
|
|
16
|
+
* @example
|
|
17
|
+
* const apiKey = extractApiKey(request);
|
|
18
|
+
*/
|
|
19
|
+
function extractApiKey(request) {
|
|
20
|
+
return request.headers.get('X-API-Key');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Hashes an API key using SHA-256 for secure storage.
|
|
24
|
+
* Store this hash in your database, never the raw key.
|
|
25
|
+
* @param apiKey - The raw API key to hash
|
|
26
|
+
* @returns The SHA-256 hash as a hex string
|
|
27
|
+
* @example
|
|
28
|
+
* const hash = hashApiKey(rawKey);
|
|
29
|
+
* await db.save({ apiKeyHash: hash });
|
|
30
|
+
*/
|
|
31
|
+
function hashApiKey(apiKey) {
|
|
32
|
+
return (0, crypto_1.createHash)('sha256').update(apiKey).digest('hex');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validates an API key against a stored hash.
|
|
36
|
+
* @param apiKey - The API key from the request
|
|
37
|
+
* @param storedHash - The hash stored in your database
|
|
38
|
+
* @returns Result with ok=true if valid, or error message if invalid
|
|
39
|
+
* @example
|
|
40
|
+
* const result = validateApiKey(apiKey, user.apiKeyHash);
|
|
41
|
+
* if (!result.ok) return httpUnauthorized();
|
|
42
|
+
*/
|
|
43
|
+
function validateApiKey(apiKey, storedHash) {
|
|
44
|
+
const keyHash = hashApiKey(apiKey);
|
|
45
|
+
if (keyHash === storedHash) {
|
|
46
|
+
return (0, types_1.okVoid)();
|
|
47
|
+
}
|
|
48
|
+
return (0, types_1.err)('Invalid API key');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates a cryptographically secure API key.
|
|
52
|
+
* @returns A random 64-character hex string (32 bytes)
|
|
53
|
+
* @example
|
|
54
|
+
* const apiKey = generateApiKey();
|
|
55
|
+
* // Return to user once, store hash in database
|
|
56
|
+
*/
|
|
57
|
+
function generateApiKey() {
|
|
58
|
+
return (0, crypto_1.randomBytes)(32).toString('hex');
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateApiKey = exports.validateApiKey = exports.hashApiKey = exports.extractApiKey = exports.generateLongLivedToken = exports.generateToken = exports.validateToken = exports.extractToken = exports.getJwtSecret = exports.initAuth = void 0;
|
|
4
|
+
var token_1 = require("./token");
|
|
5
|
+
Object.defineProperty(exports, "initAuth", { enumerable: true, get: function () { return token_1.initAuth; } });
|
|
6
|
+
Object.defineProperty(exports, "getJwtSecret", { enumerable: true, get: function () { return token_1.getJwtSecret; } });
|
|
7
|
+
Object.defineProperty(exports, "extractToken", { enumerable: true, get: function () { return token_1.extractToken; } });
|
|
8
|
+
Object.defineProperty(exports, "validateToken", { enumerable: true, get: function () { return token_1.validateToken; } });
|
|
9
|
+
Object.defineProperty(exports, "generateToken", { enumerable: true, get: function () { return token_1.generateToken; } });
|
|
10
|
+
Object.defineProperty(exports, "generateLongLivedToken", { enumerable: true, get: function () { return token_1.generateLongLivedToken; } });
|
|
11
|
+
var apiKey_1 = require("./apiKey");
|
|
12
|
+
Object.defineProperty(exports, "extractApiKey", { enumerable: true, get: function () { return apiKey_1.extractApiKey; } });
|
|
13
|
+
Object.defineProperty(exports, "hashApiKey", { enumerable: true, get: function () { return apiKey_1.hashApiKey; } });
|
|
14
|
+
Object.defineProperty(exports, "validateApiKey", { enumerable: true, get: function () { return apiKey_1.validateApiKey; } });
|
|
15
|
+
Object.defineProperty(exports, "generateApiKey", { enumerable: true, get: function () { return apiKey_1.generateApiKey; } });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Result } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Initializes the JWT authentication system. Call once at application startup.
|
|
4
|
+
* @param secret - The JWT secret key (from environment variable)
|
|
5
|
+
* @param minLength - Minimum required secret length (default: 32)
|
|
6
|
+
* @throws Error if secret is missing or too short
|
|
7
|
+
* @example
|
|
8
|
+
* initAuth(process.env.JWT_SECRET);
|
|
9
|
+
*/
|
|
10
|
+
export declare function initAuth(secret: string | undefined, minLength?: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Gets the configured JWT secret.
|
|
13
|
+
* @returns The JWT secret string
|
|
14
|
+
* @throws Error if initAuth() has not been called
|
|
15
|
+
*/
|
|
16
|
+
export declare function getJwtSecret(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the Bearer token from an Authorization header.
|
|
19
|
+
* @param authHeader - The Authorization header value
|
|
20
|
+
* @returns The token string, or null if not a valid Bearer token
|
|
21
|
+
* @example
|
|
22
|
+
* const token = extractToken(request.headers.get('Authorization'));
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractToken(authHeader: string | null): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Validates and decodes a JWT token.
|
|
27
|
+
* @typeParam T - The expected payload type (extends object)
|
|
28
|
+
* @param token - The JWT token string to validate
|
|
29
|
+
* @returns Result with decoded payload on success, or error message on failure
|
|
30
|
+
* @example
|
|
31
|
+
* const result = validateToken<UserPayload>(token);
|
|
32
|
+
* if (result.ok) {
|
|
33
|
+
* console.log(result.data.username);
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateToken<T extends object>(token: string): Result<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Generates a signed JWT token with the given payload.
|
|
39
|
+
* @typeParam T - The payload type (extends object)
|
|
40
|
+
* @param payload - The data to encode in the token
|
|
41
|
+
* @param expiresIn - Token expiration time (default: '7d')
|
|
42
|
+
* @returns The signed JWT token string
|
|
43
|
+
* @example
|
|
44
|
+
* const token = generateToken({ userId: '123', role: 'admin' }, '1h');
|
|
45
|
+
*/
|
|
46
|
+
export declare function generateToken<T extends object>(payload: T, expiresIn?: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Generates a long-lived JWT token for machine/API access.
|
|
49
|
+
* @typeParam T - The payload type (extends object)
|
|
50
|
+
* @param payload - The data to encode in the token
|
|
51
|
+
* @param expiresInDays - Token expiration in days (default: 3650 ≈ 10 years)
|
|
52
|
+
* @returns The signed JWT token string
|
|
53
|
+
* @example
|
|
54
|
+
* const apiToken = generateLongLivedToken({ machineId: 'server-1' });
|
|
55
|
+
*/
|
|
56
|
+
export declare function generateLongLivedToken<T extends object>(payload: T, expiresInDays?: number): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initAuth = initAuth;
|
|
7
|
+
exports.getJwtSecret = getJwtSecret;
|
|
8
|
+
exports.extractToken = extractToken;
|
|
9
|
+
exports.validateToken = validateToken;
|
|
10
|
+
exports.generateToken = generateToken;
|
|
11
|
+
exports.generateLongLivedToken = generateLongLivedToken;
|
|
12
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
13
|
+
const types_1 = require("../../types");
|
|
14
|
+
/**
|
|
15
|
+
* JWT token utilities - works with any payload structure
|
|
16
|
+
* Use BaseJwtPayload or extend it for type safety
|
|
17
|
+
*/
|
|
18
|
+
let jwtSecret = null;
|
|
19
|
+
/**
|
|
20
|
+
* Initializes the JWT authentication system. Call once at application startup.
|
|
21
|
+
* @param secret - The JWT secret key (from environment variable)
|
|
22
|
+
* @param minLength - Minimum required secret length (default: 32)
|
|
23
|
+
* @throws Error if secret is missing or too short
|
|
24
|
+
* @example
|
|
25
|
+
* initAuth(process.env.JWT_SECRET);
|
|
26
|
+
*/
|
|
27
|
+
function initAuth(secret, minLength = 32) {
|
|
28
|
+
if (!secret || secret.length < minLength) {
|
|
29
|
+
throw new Error(`JWT_SECRET must be at least ${minLength} characters`);
|
|
30
|
+
}
|
|
31
|
+
jwtSecret = secret;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Gets the configured JWT secret.
|
|
35
|
+
* @returns The JWT secret string
|
|
36
|
+
* @throws Error if initAuth() has not been called
|
|
37
|
+
*/
|
|
38
|
+
function getJwtSecret() {
|
|
39
|
+
if (!jwtSecret) {
|
|
40
|
+
throw new Error('Auth not initialized. Call initAuth() first.');
|
|
41
|
+
}
|
|
42
|
+
return jwtSecret;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Extracts the Bearer token from an Authorization header.
|
|
46
|
+
* @param authHeader - The Authorization header value
|
|
47
|
+
* @returns The token string, or null if not a valid Bearer token
|
|
48
|
+
* @example
|
|
49
|
+
* const token = extractToken(request.headers.get('Authorization'));
|
|
50
|
+
*/
|
|
51
|
+
function extractToken(authHeader) {
|
|
52
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return authHeader.slice(7);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validates and decodes a JWT token.
|
|
59
|
+
* @typeParam T - The expected payload type (extends object)
|
|
60
|
+
* @param token - The JWT token string to validate
|
|
61
|
+
* @returns Result with decoded payload on success, or error message on failure
|
|
62
|
+
* @example
|
|
63
|
+
* const result = validateToken<UserPayload>(token);
|
|
64
|
+
* if (result.ok) {
|
|
65
|
+
* console.log(result.data.username);
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
function validateToken(token) {
|
|
69
|
+
try {
|
|
70
|
+
const payload = jsonwebtoken_1.default.verify(token, getJwtSecret());
|
|
71
|
+
if (typeof payload === 'object' && payload !== null) {
|
|
72
|
+
return (0, types_1.ok)(payload);
|
|
73
|
+
}
|
|
74
|
+
return (0, types_1.err)('Invalid token payload');
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : 'Token validation failed';
|
|
78
|
+
return (0, types_1.err)(message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generates a signed JWT token with the given payload.
|
|
83
|
+
* @typeParam T - The payload type (extends object)
|
|
84
|
+
* @param payload - The data to encode in the token
|
|
85
|
+
* @param expiresIn - Token expiration time (default: '7d')
|
|
86
|
+
* @returns The signed JWT token string
|
|
87
|
+
* @example
|
|
88
|
+
* const token = generateToken({ userId: '123', role: 'admin' }, '1h');
|
|
89
|
+
*/
|
|
90
|
+
function generateToken(payload, expiresIn = '7d') {
|
|
91
|
+
return jsonwebtoken_1.default.sign(payload, getJwtSecret(), { expiresIn });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Generates a long-lived JWT token for machine/API access.
|
|
95
|
+
* @typeParam T - The payload type (extends object)
|
|
96
|
+
* @param payload - The data to encode in the token
|
|
97
|
+
* @param expiresInDays - Token expiration in days (default: 3650 ≈ 10 years)
|
|
98
|
+
* @returns The signed JWT token string
|
|
99
|
+
* @example
|
|
100
|
+
* const apiToken = generateLongLivedToken({ machineId: 'server-1' });
|
|
101
|
+
*/
|
|
102
|
+
function generateLongLivedToken(payload, expiresInDays = 3650) {
|
|
103
|
+
return jsonwebtoken_1.default.sign(payload, getJwtSecret(), { expiresIn: `${expiresInDays}d` });
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { badRequestResponse, unauthorizedResponse, forbiddenResponse, notFoundResponse, conflictResponse, handleFunctionError, isNotFoundError, isConflictError } from './responses';
|