@owlmeans/api-config 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +540 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +4 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +8 -0
- package/build/consts.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/modules.d.ts +2 -0
- package/build/modules.d.ts.map +1 -0
- package/build/modules.js +7 -0
- package/build/modules.js.map +1 -0
- package/build/types.d.ts +4 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +35 -0
- package/src/consts.ts +10 -0
- package/src/index.ts +5 -0
- package/src/modules.ts +7 -0
- package/src/types.ts +7 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# @owlmeans/api-config
|
|
2
|
+
|
|
3
|
+
Shared API configuration library for exposing safe configuration data to clients. This package provides a standardized way to advertise configuration information through REST APIs while filtering out sensitive server-side configuration details.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/api-config` package enables safe sharing of configuration data between server and client applications. It provides:
|
|
8
|
+
|
|
9
|
+
- **Configuration Filtering**: Automatically filters out sensitive server-side configuration
|
|
10
|
+
- **API Endpoint**: Standard `/assets/config.json` endpoint for configuration delivery
|
|
11
|
+
- **Type Safety**: TypeScript interfaces for API-safe configuration
|
|
12
|
+
- **Selective Exposure**: Granular control over what configuration is exposed
|
|
13
|
+
- **Module Integration**: Ready-to-use modules for configuration endpoints
|
|
14
|
+
- **Cross-Environment Support**: Works with both server-side and client-side implementations
|
|
15
|
+
|
|
16
|
+
This package is part of the OwlMeans configuration ecosystem:
|
|
17
|
+
- **@owlmeans/config**: Core configuration management
|
|
18
|
+
- **@owlmeans/api-config**: Shared API configuration *(this package)*
|
|
19
|
+
- **@owlmeans/api-config-server**: Server-side API configuration
|
|
20
|
+
- **@owlmeans/api-config-client**: Client-side API configuration consumption
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @owlmeans/api-config
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Dependencies
|
|
29
|
+
|
|
30
|
+
This package extends:
|
|
31
|
+
- `@owlmeans/config`: Core configuration system
|
|
32
|
+
- `@owlmeans/module`: Module system for API endpoints
|
|
33
|
+
- `@owlmeans/route`: Routing infrastructure
|
|
34
|
+
|
|
35
|
+
## Core Concepts
|
|
36
|
+
|
|
37
|
+
### API-Safe Configuration
|
|
38
|
+
|
|
39
|
+
API configuration removes sensitive server-side details like database connections, trusted entities, and internal service configurations while preserving client-relevant settings.
|
|
40
|
+
|
|
41
|
+
### Configuration Filtering
|
|
42
|
+
|
|
43
|
+
The package automatically filters out sensitive configuration keys and only exposes safe configuration data through the API.
|
|
44
|
+
|
|
45
|
+
### Standardized Endpoint
|
|
46
|
+
|
|
47
|
+
The `/assets/config.json` endpoint provides a standard location for clients to retrieve configuration data.
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
|
|
51
|
+
### Types
|
|
52
|
+
|
|
53
|
+
#### `ApiConfig`
|
|
54
|
+
|
|
55
|
+
Interface for API-safe configuration that extends CommonConfig while omitting sensitive fields.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
interface ApiConfig extends Omit<
|
|
59
|
+
CommonConfig,
|
|
60
|
+
'dbs' | 'trusted' | 'ready' | 'service' | 'layer' | 'type' | 'layerId'
|
|
61
|
+
> {}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Excluded Fields:**
|
|
65
|
+
- `dbs`: Database configurations (sensitive)
|
|
66
|
+
- `trusted`: Trusted entity configurations (sensitive)
|
|
67
|
+
- `ready`: Internal readiness state (internal)
|
|
68
|
+
- `service`: Service name (internal)
|
|
69
|
+
- `layer`: Service layer (internal)
|
|
70
|
+
- `type`: Application type (internal)
|
|
71
|
+
- `layerId`: Layer identifier (internal)
|
|
72
|
+
|
|
73
|
+
**Included Fields:**
|
|
74
|
+
- `debug`: Debug configuration settings
|
|
75
|
+
- `security`: Public security settings
|
|
76
|
+
- `brand`: Branding configuration
|
|
77
|
+
- `defaultEntityId`: Default entity identifier
|
|
78
|
+
- `plugins`: Public plugin configurations
|
|
79
|
+
- Configuration records marked as safe
|
|
80
|
+
|
|
81
|
+
### Constants
|
|
82
|
+
|
|
83
|
+
#### Configuration Filtering
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const API_CONFIG = 'api-config:advertise' // Module alias for config endpoint
|
|
87
|
+
|
|
88
|
+
const notAdvertizedConfigKeys = [
|
|
89
|
+
'dbs', // Database configurations
|
|
90
|
+
'trusted', // Trusted entities
|
|
91
|
+
'ready', // Readiness state
|
|
92
|
+
'service', // Service name
|
|
93
|
+
'layer', // Service layer
|
|
94
|
+
'type', // Application type
|
|
95
|
+
'layerId', // Layer ID
|
|
96
|
+
'records', // Internal records
|
|
97
|
+
'webService', // Web service configs
|
|
98
|
+
'oidc', // OIDC configurations
|
|
99
|
+
'storageBuckets' // Storage bucket configs
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
const allowedConfigRecords = [
|
|
103
|
+
'plan', // Subscription/plan information
|
|
104
|
+
'product', // Product configuration
|
|
105
|
+
'l10n' // Localization settings
|
|
106
|
+
]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Modules
|
|
110
|
+
|
|
111
|
+
#### Configuration Endpoint Module
|
|
112
|
+
|
|
113
|
+
The package provides a ready-to-use module for serving configuration through the standard endpoint.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { modules } from '@owlmeans/api-config'
|
|
117
|
+
|
|
118
|
+
// The modules array contains:
|
|
119
|
+
// - API config endpoint at /assets/config.json (sticky: true)
|
|
120
|
+
|
|
121
|
+
export const modules = [
|
|
122
|
+
module(route(API_CONFIG, '/assets/config.json'), { sticky: true })
|
|
123
|
+
]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Module Properties:**
|
|
127
|
+
- **Route**: `/assets/config.json` - Standard configuration endpoint
|
|
128
|
+
- **Sticky**: `true` - Applied to all requests regardless of other routing
|
|
129
|
+
- **Method**: `GET` (default) - Standard HTTP GET endpoint
|
|
130
|
+
|
|
131
|
+
## Usage Examples
|
|
132
|
+
|
|
133
|
+
### Basic Module Registration
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { modules } from '@owlmeans/api-config'
|
|
137
|
+
import { makeServerContext } from '@owlmeans/server-context'
|
|
138
|
+
|
|
139
|
+
// Create server context
|
|
140
|
+
const context = makeServerContext(config)
|
|
141
|
+
|
|
142
|
+
// Register API config modules
|
|
143
|
+
context.registerModules(modules)
|
|
144
|
+
|
|
145
|
+
// Initialize context
|
|
146
|
+
await context.configure().init()
|
|
147
|
+
|
|
148
|
+
// The /assets/config.json endpoint is now available
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Custom Configuration Filtering
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { ApiConfig, notAdvertizedConfigKeys, allowedConfigRecords } from '@owlmeans/api-config'
|
|
155
|
+
import { CommonConfig } from '@owlmeans/config'
|
|
156
|
+
|
|
157
|
+
// Function to create API-safe configuration
|
|
158
|
+
function createApiConfig(fullConfig: CommonConfig): ApiConfig {
|
|
159
|
+
const apiConfig: Partial<ApiConfig> = {}
|
|
160
|
+
|
|
161
|
+
// Copy all non-sensitive configuration
|
|
162
|
+
Object.entries(fullConfig).forEach(([key, value]) => {
|
|
163
|
+
if (!notAdvertizedConfigKeys.includes(key)) {
|
|
164
|
+
(apiConfig as any)[key] = value
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Filter configuration records to only allowed types
|
|
169
|
+
if (fullConfig.records) {
|
|
170
|
+
const safeRecords = Object.entries(fullConfig.records)
|
|
171
|
+
.filter(([recordType]) => allowedConfigRecords.includes(recordType))
|
|
172
|
+
.reduce((acc, [recordType, records]) => ({
|
|
173
|
+
...acc,
|
|
174
|
+
[recordType]: records
|
|
175
|
+
}), {})
|
|
176
|
+
|
|
177
|
+
if (Object.keys(safeRecords).length > 0) {
|
|
178
|
+
apiConfig.records = safeRecords
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return apiConfig as ApiConfig
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Usage
|
|
186
|
+
const fullConfig = makeConfig(AppType.Backend, 'my-service', {
|
|
187
|
+
// Full server configuration with sensitive data
|
|
188
|
+
dbs: [{ /* database config */ }],
|
|
189
|
+
trusted: [{ /* trusted entities */ }],
|
|
190
|
+
brand: { home: '/dashboard' },
|
|
191
|
+
debug: { enabled: true }
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const apiConfig = createApiConfig(fullConfig)
|
|
195
|
+
// apiConfig only contains safe configuration for API exposure
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Server-Side Implementation
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { modules, ApiConfig } from '@owlmeans/api-config'
|
|
202
|
+
import { handleRequest } from '@owlmeans/server-api'
|
|
203
|
+
|
|
204
|
+
// Create custom handler for configuration endpoint
|
|
205
|
+
const configHandler = handleRequest(async (req, ctx) => {
|
|
206
|
+
const fullConfig = ctx.cfg
|
|
207
|
+
|
|
208
|
+
// Create API-safe configuration
|
|
209
|
+
const apiConfig: ApiConfig = {
|
|
210
|
+
debug: fullConfig.debug,
|
|
211
|
+
security: fullConfig.security ? {
|
|
212
|
+
// Only expose public security settings
|
|
213
|
+
auth: fullConfig.security.auth
|
|
214
|
+
} : undefined,
|
|
215
|
+
brand: fullConfig.brand,
|
|
216
|
+
defaultEntityId: fullConfig.defaultEntityId,
|
|
217
|
+
plugins: fullConfig.plugins?.filter(plugin => !plugin.internal)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Add allowed configuration records
|
|
221
|
+
if (fullConfig.records) {
|
|
222
|
+
const allowedRecords = ['plan', 'product', 'l10n']
|
|
223
|
+
apiConfig.records = Object.entries(fullConfig.records)
|
|
224
|
+
.filter(([type]) => allowedRecords.includes(type))
|
|
225
|
+
.reduce((acc, [type, records]) => ({ ...acc, [type]: records }), {})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return apiConfig
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// Register module with custom handler
|
|
232
|
+
const configModule = module(
|
|
233
|
+
route('api-config', '/assets/config.json'),
|
|
234
|
+
{ handle: configHandler, sticky: true }
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
context.registerModule(configModule)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Client-Side Usage Pattern
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Example of how clients would consume the API config
|
|
244
|
+
// (Implementation would be in @owlmeans/api-config-client)
|
|
245
|
+
|
|
246
|
+
interface ClientConfigConsumer {
|
|
247
|
+
loadConfig(): Promise<ApiConfig>
|
|
248
|
+
getDebugSettings(): boolean
|
|
249
|
+
getBrandSettings(): BrandSettings
|
|
250
|
+
getSecuritySettings(): SecurityConfig
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
class ConfigService implements ClientConfigConsumer {
|
|
254
|
+
private config?: ApiConfig
|
|
255
|
+
|
|
256
|
+
async loadConfig(): Promise<ApiConfig> {
|
|
257
|
+
const response = await fetch('/assets/config.json')
|
|
258
|
+
this.config = await response.json()
|
|
259
|
+
return this.config
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getDebugSettings(): boolean {
|
|
263
|
+
return this.config?.debug?.enabled ?? false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getBrandSettings(): BrandSettings {
|
|
267
|
+
return this.config?.brand ?? {}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
getSecuritySettings(): SecurityConfig {
|
|
271
|
+
return this.config?.security ?? {}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Multi-Environment Configuration
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { ApiConfig } from '@owlmeans/api-config'
|
|
280
|
+
|
|
281
|
+
// Different API configurations for different environments
|
|
282
|
+
const createEnvironmentApiConfig = (
|
|
283
|
+
env: 'development' | 'staging' | 'production',
|
|
284
|
+
baseConfig: CommonConfig
|
|
285
|
+
): ApiConfig => {
|
|
286
|
+
const apiConfig: ApiConfig = {
|
|
287
|
+
debug: env === 'development' ? { enabled: true, all: true } : { enabled: false },
|
|
288
|
+
security: {
|
|
289
|
+
auth: baseConfig.security?.auth
|
|
290
|
+
},
|
|
291
|
+
brand: baseConfig.brand
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Add environment-specific settings
|
|
295
|
+
if (env === 'development') {
|
|
296
|
+
apiConfig.debug = { ...apiConfig.debug, i18n: true }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return apiConfig
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Usage in different environments
|
|
303
|
+
const devApiConfig = createEnvironmentApiConfig('development', serverConfig)
|
|
304
|
+
const prodApiConfig = createEnvironmentApiConfig('production', serverConfig)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Configuration Record Filtering
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { allowedConfigRecords } from '@owlmeans/api-config'
|
|
311
|
+
|
|
312
|
+
// Example configuration with various record types
|
|
313
|
+
const serverConfig = {
|
|
314
|
+
records: {
|
|
315
|
+
// Allowed records (will be exposed)
|
|
316
|
+
plan: [
|
|
317
|
+
{ id: 'basic', name: 'Basic Plan', features: ['feature1'] },
|
|
318
|
+
{ id: 'premium', name: 'Premium Plan', features: ['feature1', 'feature2'] }
|
|
319
|
+
],
|
|
320
|
+
product: [
|
|
321
|
+
{ id: 'main', name: 'Main Product', version: '1.0.0' }
|
|
322
|
+
],
|
|
323
|
+
l10n: [
|
|
324
|
+
{ locale: 'en', name: 'English' },
|
|
325
|
+
{ locale: 'es', name: 'Spanish' }
|
|
326
|
+
],
|
|
327
|
+
|
|
328
|
+
// Not allowed records (will be filtered out)
|
|
329
|
+
internal: [
|
|
330
|
+
{ secret: 'sensitive-data' }
|
|
331
|
+
],
|
|
332
|
+
database: [
|
|
333
|
+
{ connection: 'mongodb://secret' }
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Function to filter records
|
|
339
|
+
const filterConfigRecords = (records: any) => {
|
|
340
|
+
return Object.entries(records)
|
|
341
|
+
.filter(([recordType]) => allowedConfigRecords.includes(recordType))
|
|
342
|
+
.reduce((acc, [type, data]) => ({ ...acc, [type]: data }), {})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const safeRecords = filterConfigRecords(serverConfig.records)
|
|
346
|
+
// Only contains: plan, product, l10n records
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Plugin Configuration Exposure
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { ApiConfig } from '@owlmeans/api-config'
|
|
353
|
+
|
|
354
|
+
// Example of exposing safe plugin configurations
|
|
355
|
+
const createApiConfigWithPlugins = (serverConfig: CommonConfig): ApiConfig => {
|
|
356
|
+
return {
|
|
357
|
+
debug: serverConfig.debug,
|
|
358
|
+
brand: serverConfig.brand,
|
|
359
|
+
|
|
360
|
+
// Filter plugins to only expose public ones
|
|
361
|
+
plugins: serverConfig.plugins?.filter(plugin => {
|
|
362
|
+
// Only expose plugins explicitly marked as public
|
|
363
|
+
return plugin.public === true && !plugin.internal
|
|
364
|
+
}).map(plugin => ({
|
|
365
|
+
// Remove sensitive plugin data
|
|
366
|
+
id: plugin.id,
|
|
367
|
+
type: plugin.type,
|
|
368
|
+
value: plugin.publicValue || plugin.value,
|
|
369
|
+
config: plugin.publicConfig
|
|
370
|
+
}))
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Security Considerations
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { ApiConfig, notAdvertizedConfigKeys } from '@owlmeans/api-config'
|
|
379
|
+
|
|
380
|
+
// Ensure sensitive data is never exposed
|
|
381
|
+
const secureApiConfigFilter = (config: any): ApiConfig => {
|
|
382
|
+
// Double-check sensitive keys are removed
|
|
383
|
+
const filtered = { ...config }
|
|
384
|
+
|
|
385
|
+
notAdvertizedConfigKeys.forEach(key => {
|
|
386
|
+
delete filtered[key]
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// Additional security checks
|
|
390
|
+
if (filtered.security) {
|
|
391
|
+
// Remove sensitive security settings
|
|
392
|
+
delete filtered.security.keys
|
|
393
|
+
delete filtered.security.secrets
|
|
394
|
+
delete filtered.security.internalAuth
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Remove any remaining sensitive patterns
|
|
398
|
+
const sanitized = JSON.parse(JSON.stringify(filtered, (key, value) => {
|
|
399
|
+
// Filter out any keys containing sensitive terms
|
|
400
|
+
if (key.toLowerCase().includes('secret') ||
|
|
401
|
+
key.toLowerCase().includes('password') ||
|
|
402
|
+
key.toLowerCase().includes('token') ||
|
|
403
|
+
key.toLowerCase().includes('key')) {
|
|
404
|
+
return undefined
|
|
405
|
+
}
|
|
406
|
+
return value
|
|
407
|
+
}))
|
|
408
|
+
|
|
409
|
+
return sanitized
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Integration Patterns
|
|
414
|
+
|
|
415
|
+
### With Server API
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { modules } from '@owlmeans/api-config'
|
|
419
|
+
import { createApiServer } from '@owlmeans/server-api'
|
|
420
|
+
|
|
421
|
+
const apiServer = createApiServer('main')
|
|
422
|
+
const context = makeServerContext(config)
|
|
423
|
+
|
|
424
|
+
// Register API server and config modules
|
|
425
|
+
context.registerService(apiServer)
|
|
426
|
+
context.registerModules(modules)
|
|
427
|
+
|
|
428
|
+
await context.configure().init()
|
|
429
|
+
await apiServer.listen()
|
|
430
|
+
|
|
431
|
+
// Config available at: GET /assets/config.json
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### With Authentication
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
import { module, guard } from '@owlmeans/module'
|
|
438
|
+
import { route } from '@owlmeans/route'
|
|
439
|
+
|
|
440
|
+
// Protected configuration endpoint
|
|
441
|
+
const protectedConfigModule = module(
|
|
442
|
+
route('protected-config', '/admin/config.json'),
|
|
443
|
+
{
|
|
444
|
+
...guard('admin'),
|
|
445
|
+
handle: handleRequest(async (req, ctx) => {
|
|
446
|
+
// Return full configuration for admin users
|
|
447
|
+
return createFullApiConfig(ctx.cfg)
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
// Public configuration endpoint (using standard module)
|
|
453
|
+
const publicConfigModule = modules[0] // Standard config module
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### With Caching
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { module } from '@owlmeans/module'
|
|
460
|
+
import { handleRequest } from '@owlmeans/server-api'
|
|
461
|
+
|
|
462
|
+
// Cached configuration endpoint
|
|
463
|
+
const cachedConfigModule = module(
|
|
464
|
+
route('cached-config', '/assets/config.json'),
|
|
465
|
+
{
|
|
466
|
+
sticky: true,
|
|
467
|
+
handle: handleRequest(async (req, ctx) => {
|
|
468
|
+
// Check cache first
|
|
469
|
+
const cacheKey = 'api-config'
|
|
470
|
+
const cached = await ctx.service('cache').get(cacheKey)
|
|
471
|
+
|
|
472
|
+
if (cached) {
|
|
473
|
+
return cached
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Generate fresh config
|
|
477
|
+
const apiConfig = createApiConfig(ctx.cfg)
|
|
478
|
+
|
|
479
|
+
// Cache for 5 minutes
|
|
480
|
+
await ctx.service('cache').set(cacheKey, apiConfig, 300)
|
|
481
|
+
|
|
482
|
+
return apiConfig
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
## Best Practices
|
|
489
|
+
|
|
490
|
+
1. **Security First**: Always verify sensitive data is filtered out
|
|
491
|
+
2. **Environment Awareness**: Adapt configuration exposure based on environment
|
|
492
|
+
3. **Minimal Exposure**: Only expose configuration data that clients actually need
|
|
493
|
+
4. **Validation**: Validate API configuration before exposure
|
|
494
|
+
5. **Caching**: Consider caching configuration for performance
|
|
495
|
+
6. **Versioning**: Consider versioning configuration APIs for compatibility
|
|
496
|
+
7. **Documentation**: Document what configuration is available to clients
|
|
497
|
+
|
|
498
|
+
## Error Handling
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
import { handleRequest } from '@owlmeans/server-api'
|
|
502
|
+
|
|
503
|
+
const configHandler = handleRequest(async (req, ctx) => {
|
|
504
|
+
try {
|
|
505
|
+
const apiConfig = createApiConfig(ctx.cfg)
|
|
506
|
+
|
|
507
|
+
// Validate the configuration before sending
|
|
508
|
+
if (!apiConfig || typeof apiConfig !== 'object') {
|
|
509
|
+
throw new Error('Invalid configuration generated')
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return apiConfig
|
|
513
|
+
} catch (error) {
|
|
514
|
+
// Log error but don't expose details
|
|
515
|
+
console.error('Config generation error:', error)
|
|
516
|
+
|
|
517
|
+
// Return minimal safe config
|
|
518
|
+
return {
|
|
519
|
+
debug: { enabled: false },
|
|
520
|
+
brand: {},
|
|
521
|
+
error: 'Configuration temporarily unavailable'
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Performance Considerations
|
|
528
|
+
|
|
529
|
+
- **Caching**: Configuration changes infrequently, consider caching
|
|
530
|
+
- **Size**: Keep API configuration minimal to reduce transfer size
|
|
531
|
+
- **Compression**: Use gzip compression for the JSON endpoint
|
|
532
|
+
- **CDN**: Consider CDN caching for static configuration
|
|
533
|
+
|
|
534
|
+
## Related Packages
|
|
535
|
+
|
|
536
|
+
- [`@owlmeans/config`](../config) - Core configuration management
|
|
537
|
+
- [`@owlmeans/api-config-server`](../api-config-server) - Server-side implementation
|
|
538
|
+
- [`@owlmeans/api-config-client`](../api-config-client) - Client-side consumption
|
|
539
|
+
- [`@owlmeans/module`](../module) - Module system for endpoints
|
|
540
|
+
- [`@owlmeans/route`](../route) - Routing infrastructure
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,UAAU,yBAAyB,CAAA;AAEhD,eAAO,MAAM,uBAAuB,UAEnC,CAAA;AAED,eAAO,MAAM,oBAAoB,UAEhC,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const API_CONFIG = 'api-config:advertise';
|
|
2
|
+
export const notAdvertizedConfigKeys = [
|
|
3
|
+
'dbs', 'trusted', 'ready', 'service', 'layer', 'type', 'layerId', 'records', 'webService', 'oidc', 'storageBuckets'
|
|
4
|
+
];
|
|
5
|
+
export const allowedConfigRecords = [
|
|
6
|
+
'plan', 'product', 'l10n'
|
|
7
|
+
];
|
|
8
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAA;AAEhD,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,gBAAgB;CACpH,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM,EAAE,SAAS,EAAE,MAAM;CAC1B,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,2CAEnB,CAAA"}
|
package/build/modules.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { module } from '@owlmeans/module';
|
|
2
|
+
import { route } from '@owlmeans/route';
|
|
3
|
+
import { API_CONFIG } from './consts.js';
|
|
4
|
+
export const modules = [
|
|
5
|
+
module(route(API_CONFIG, '/assets/config.json'), { sticky: true }),
|
|
6
|
+
];
|
|
7
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;CACnE,CAAA"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,MAAM,WAAW,SAAU,SAAQ,IAAI,CACrC,YAAY,EACZ,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CACvE;CACA"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/api-config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 6 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@owlmeans/config": "^0.1.0",
|
|
24
|
+
"@owlmeans/module": "^0.1.0",
|
|
25
|
+
"@owlmeans/route": "^0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"nodemon": "^3.1.7",
|
|
29
|
+
"typescript": "^5.6.3"
|
|
30
|
+
},
|
|
31
|
+
"private": false,
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/consts.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
export const API_CONFIG = 'api-config:advertise'
|
|
3
|
+
|
|
4
|
+
export const notAdvertizedConfigKeys = [
|
|
5
|
+
'dbs', 'trusted', 'ready', 'service', 'layer', 'type', 'layerId', 'records', 'webService', 'oidc', 'storageBuckets'
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
export const allowedConfigRecords = [
|
|
9
|
+
'plan', 'product', 'l10n'
|
|
10
|
+
]
|
package/src/index.ts
ADDED
package/src/modules.ts
ADDED
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
7
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
8
|
+
},
|
|
9
|
+
"exclude": [
|
|
10
|
+
"./dist/**/*",
|
|
11
|
+
"./build/**/*",
|
|
12
|
+
"./*.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/consts.ts","./src/index.ts","./src/modules.ts","./src/types.ts"],"version":"5.6.3"}
|