@sap/cds 7.4.1 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/apis/cds.d.ts +1 -38
  3. package/apis/core.d.ts +21 -101
  4. package/apis/cqn.d.ts +18 -76
  5. package/apis/csn.d.ts +18 -114
  6. package/apis/events.d.ts +16 -123
  7. package/apis/internal/inference.d.ts +18 -32
  8. package/apis/linked.d.ts +18 -97
  9. package/apis/log.d.ts +19 -164
  10. package/apis/models.d.ts +18 -180
  11. package/apis/ql.d.ts +16 -322
  12. package/apis/reflect.d.ts +32 -0
  13. package/apis/server.d.ts +18 -135
  14. package/apis/services.d.ts +18 -380
  15. package/bin/cds-serve.js +5 -2
  16. package/bin/serve.js +7 -16
  17. package/lib/auth/basic-auth.js +3 -1
  18. package/lib/auth/ias-auth.js +62 -48
  19. package/lib/auth/ias-claims.js +34 -0
  20. package/lib/auth/index.js +54 -33
  21. package/lib/auth/jwt-auth.js +55 -52
  22. package/lib/compile/cdsc.js +2 -2
  23. package/lib/compile/to/edm.js +4 -4
  24. package/lib/compile/to/hdbtabledata.js +5 -8
  25. package/lib/compile/to/srvinfo.js +2 -2
  26. package/lib/env/cds-env.js +3 -9
  27. package/lib/env/cds-requires.js +16 -17
  28. package/lib/env/compat.js +0 -9
  29. package/lib/env/defaults.js +17 -6
  30. package/lib/i18n/localize.js +46 -42
  31. package/lib/index.js +6 -8
  32. package/lib/linked/classes.js +7 -118
  33. package/lib/linked/entities.js +1 -1
  34. package/lib/log/cds-log.js +15 -10
  35. package/lib/log/format/aspects/als.js +41 -0
  36. package/lib/log/format/aspects/cf.js +36 -0
  37. package/lib/log/format/json.js +96 -0
  38. package/lib/plugins.js +7 -3
  39. package/lib/req/context.js +4 -2
  40. package/lib/srv/cds-connect.js +3 -5
  41. package/lib/srv/cds-serve.js +13 -26
  42. package/lib/srv/factory.js +3 -3
  43. package/lib/srv/middlewares/index.js +0 -2
  44. package/lib/srv/middlewares/trace.js +2 -3
  45. package/lib/srv/protocols/_legacy.js +27 -30
  46. package/lib/srv/protocols/index.js +173 -58
  47. package/lib/srv/protocols/odata-v4.js +29 -16
  48. package/lib/srv/srv-api.js +8 -13
  49. package/lib/srv/srv-handlers.js +14 -14
  50. package/lib/utils/cds-utils.js +15 -0
  51. package/libx/_runtime/auth/index.js +4 -5
  52. package/libx/_runtime/auth/strategies/basic.js +2 -2
  53. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +23 -13
  54. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +6 -15
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +10 -3
  56. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +2 -1
  58. package/libx/_runtime/cds-services/services/utils/columns.js +3 -9
  59. package/libx/_runtime/cds.js +13 -0
  60. package/libx/_runtime/common/composition/data.js +3 -0
  61. package/libx/_runtime/common/composition/delete.js +1 -1
  62. package/libx/_runtime/common/error/frontend.js +2 -2
  63. package/libx/_runtime/common/generic/auth/readOnly.js +1 -1
  64. package/libx/_runtime/common/generic/auth/restrictions.js +1 -1
  65. package/libx/_runtime/common/generic/sorting.js +4 -5
  66. package/libx/_runtime/common/utils/csn.js +23 -18
  67. package/libx/_runtime/common/utils/propagateForeignKeys.js +2 -1
  68. package/libx/_runtime/common/utils/restrictions.js +6 -15
  69. package/libx/_runtime/db/generic/input.js +3 -2
  70. package/libx/_runtime/fiori/generic/readOverDraft.js +2 -5
  71. package/libx/_runtime/fiori/lean-draft.js +69 -5
  72. package/libx/_runtime/hana/Service.js +1 -1
  73. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  74. package/libx/_runtime/messaging/Outbox.js +3 -8
  75. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  76. package/libx/_runtime/messaging/file-based.js +1 -1
  77. package/libx/_runtime/messaging/service.js +7 -10
  78. package/libx/_runtime/remote/Service.js +15 -45
  79. package/libx/_runtime/remote/utils/client.js +20 -33
  80. package/libx/_runtime/remote/utils/cloudSdkProvider.js +30 -0
  81. package/libx/_runtime/sqlite/Service.js +2 -2
  82. package/libx/odata/afterburner.js +29 -21
  83. package/libx/odata/cqn2odata.js +1 -1
  84. package/libx/odata/error.js +7 -0
  85. package/libx/odata/grammar.peggy +16 -20
  86. package/libx/odata/metadata.js +73 -78
  87. package/libx/odata/parser.js +1 -1
  88. package/libx/odata/read.js +94 -0
  89. package/libx/odata/result.js +91 -0
  90. package/libx/odata/service-document.js +31 -37
  91. package/libx/odata/utils.js +2 -1
  92. package/libx/outbox/index.js +9 -4
  93. package/libx/rest/RestAdapter.js +68 -67
  94. package/libx/rest/middleware/create.js +20 -26
  95. package/libx/rest/middleware/delete.js +5 -3
  96. package/libx/rest/middleware/error.js +2 -3
  97. package/libx/rest/middleware/input.js +5 -5
  98. package/libx/rest/middleware/operation.js +96 -41
  99. package/libx/rest/middleware/parse.js +4 -6
  100. package/libx/rest/middleware/payload.js +5 -5
  101. package/libx/rest/middleware/read.js +11 -17
  102. package/libx/rest/middleware/update.js +20 -25
  103. package/package.json +2 -1
  104. package/server.js +7 -4
  105. package/srv/outbox.cds +9 -10
  106. package/apis/env.d.ts +0 -25
  107. package/apis/test.d.ts +0 -81
  108. package/apis/utils.d.ts +0 -15
  109. package/lib/auth/passport-basic.js +0 -14
  110. package/lib/auth/passport-digest.js +0 -16
  111. package/lib/env/presets.js +0 -35
  112. package/lib/log/format/cf.js +0 -16
  113. package/lib/log/format/kibana.js +0 -92
  114. package/lib/srv/middlewares/ctx-auth.js +0 -11
  115. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +0 -119
@@ -1,384 +1,22 @@
1
- import { SELECT, INSERT, UPDATE, DELETE, Query, ConstructedQuery, UPSERT } from './ql'
2
- import { Awaitable } from './ql'
3
- import { ArrayConstructable, Constructable } from './internal/inference'
4
- import { LinkedCSN, LinkedDefinition, Definitions, LinkedEntity } from './linked'
5
- import { CSN } from './csn'
6
- import { EventContext } from './events'
7
- import { Request } from './events'
8
-
9
- export class QueryAPI {
10
-
11
- entities : LinkedCSN['entities']
12
-
13
- /**
14
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
15
- */
16
- read: {
17
- <T extends ArrayConstructable<any>>(entity: T, key?: any): Awaitable<SELECT<T>, InstanceType<T>>
18
- <T>(entity: LinkedDefinition | string, key?: any): SELECT<T>
19
- }
20
-
21
- /**
22
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
23
- */
24
- create: {
25
- <T extends ArrayConstructable<any>>(entity: T, key?: any): INSERT<T>
26
- <T>(entity: LinkedDefinition | string, key?: any): INSERT<T>
27
- }
28
-
29
- /**
30
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
31
- */
32
- insert: {
33
- <T extends ArrayConstructable<any>>(data: T): INSERT<T>
34
- <T>(data: object | object[]): INSERT<T>
35
- }
36
-
37
- /**
38
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
39
- */
40
- upsert: {
41
- <T extends ArrayConstructable<any>>(data: T): UPSERT<T>
42
- <T>(data: object | object[]): UPSERT<T>
43
- }
44
-
45
- /**
46
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
47
- */
48
- update: {
49
- <T extends ArrayConstructable<any>>(entity: T, key?: any): UPDATE<T>
50
- <T>(entity: LinkedDefinition | string, key?: any): UPDATE<T>
51
- }
52
-
53
- /**
54
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
55
- */
56
- run: {
57
- (query: ConstructedQuery | ConstructedQuery[]): Promise<ResultSet | any>
58
- (query: Query): Promise<ResultSet | any>
59
- (query: string, args?: any[] | object): Promise<ResultSet | any>
60
- }
61
-
62
- /**
63
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api)
64
- */
65
- delete<T>(entity: LinkedDefinition | string, key?: any): DELETE<T>
66
-
67
- /**
68
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#srv-foreach-entity)
69
- */
70
- foreach(query: Query, callback: (row: object) => void): this
71
-
72
- /**
73
- * @see [docs](https://cap.cloud.sap/docs/node.js/core-services#srv-stream-column)
74
- */
75
- stream: {
76
- (column: string): {
77
- from(entity: LinkedDefinition | string): {
78
- where(filter: any): ReadableStream
79
- }
80
- }
81
- (query: Query): Promise<ReadableStream>
82
- }
83
-
84
- /**
85
- * Starts or joins a transaction
86
- * @see [docs](https://cap.cloud.sap/docs/node.js/cds-tx)
87
- */
88
-
89
- tx: {
90
- (fn: (tx: Transaction) => {}): Promise<unknown>
91
- (context?: object): Transaction
92
- (context: object, fn: (tx: Transaction) => {}): Promise<unknown>
93
- }
94
-
95
- transaction: {
96
- (fn: (tx: Transaction) => {}): Promise<unknown>
97
- (context?: object): Transaction
98
- (context: object, fn: (tx: Transaction) => {}): Promise<unknown>
99
- }
100
-
101
- /**
102
- * @see [docs](https://cap.cloud.sap/docs/node.js/cds-tx#cds-spawn)
103
- */
104
- spawn(options: {
105
- [key: string]: any
106
- every?: number
107
- after?: number
108
- }, fn: (tx: Transaction) => {}): SpawnEventEmitter
109
-
110
- /**
111
- * @see [docs](https://cap.cloud.sap/docs/node.js/cds-tx#event-contexts
112
- */
113
- context?: EventContext
114
- }
115
-
116
-
117
1
  /**
118
- * Class cds.Service
119
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
2
+ * DO NO LONGER IMPORT THIS FILE
3
+ *
4
+ * Instead use:
5
+ *
6
+ * @example
7
+ * ```
8
+ * import * as cds from '@sap/cds'
9
+ * cds.Request
10
+ * or
11
+ * import { Request } from '@sap/cds'
12
+ * or
13
+ * @type { import('@sap/cds').Request }
14
+ * ```
15
+ *
16
+ * @deprecated
120
17
  */
121
- export class Service extends QueryAPI {
122
- constructor(
123
- name?: string,
124
- model?: CSN,
125
- options?: {
126
- kind: string
127
- impl: string | ServiceImpl
128
- }
129
- )
130
-
131
- /**
132
- * The name of the service
133
- */
134
- name: string
135
-
136
- /**
137
- * The model from which the service's definition was loaded
138
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
139
- */
140
- model: LinkedCSN
141
-
142
- /**
143
- * Provides access to the entities exposed by a service
144
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
145
- */
146
- entities: Definitions & ((namespace: string) => Definitions)
147
-
148
- /**
149
- * Provides access to the events declared by a service
150
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
151
- */
152
- events: Definitions & ((namespace: string) => Definitions)
153
-
154
- /**
155
- * Provides access to the types exposed by a service
156
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
157
- */
158
- types: Definitions & ((namespace: string) => Definitions)
159
-
160
- /**
161
- * Provides access to the operations, i.e. actions and functions, exposed by a service
162
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
163
- */
164
- operations: Definitions & ((namespace: string) => Definitions)
165
-
166
- /**
167
- * Acts like a parameter-less constructor. Ensure to call `await super.init()` to have the base class’s handlers added.
168
- * You may register own handlers before the base class’s ones, to intercept requests before the default handlers snap in.
169
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services#srv-init)
170
- */
171
- init(): Promise<void>
172
-
173
- /**
174
- * Constructs and emits an asynchronous event.
175
- * @see [capire docs](https://cap.cloud.sap/docs/core-services#srv-emit-event)
176
- */
177
- emit: {
178
- <T = any>(details: { event: types.event; data?: object; headers?: object }): Promise<T>
179
- <T = any>(event: types.event, data?: object, headers?: object): Promise<T>
180
- }
181
-
182
- /**
183
- * Constructs and sends a synchronous request.
184
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services#srv-send-request)
185
- */
186
- send: {
187
- <T = any>(event: types.event, path: string, data?: object, headers?: object): Promise<T>
188
- <T = any>(event: types.event, data?: object, headers?: object): Promise<T>
189
- <T = any>(details: { event: types.event; data?: object; headers?: object }): Promise<T>
190
- <T = any>(details: { query: ConstructedQuery; data?: object; headers?: object }): Promise<T>
191
- <T = any>(details: { method: types.eventName; path: string; data?: object; headers?: object }): Promise<T>
192
- <T = any>(details: { event: types.eventName; entity: LinkedDefinition | string; data?: object; params?: object }): Promise<T>
193
- }
194
-
195
- /**
196
- * Constructs and sends a GET request.
197
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services#rest-style-api)
198
- */
199
- get<T = any>(entityOrPath: types.target, data?: object): Promise<T>
200
- /**
201
- * Constructs and sends a POST request.
202
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services#rest-style-api)
203
- */
204
- post<T = any>(entityOrPath: types.target, data?: object): Promise<T>
205
- /**
206
- * Constructs and sends a PUT request.
207
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services#rest-style-api)
208
- */
209
- put<T = any>(entityOrPath: types.target, data?: object): Promise<T>
210
- /**
211
- * Constructs and sends a PATCH request.
212
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services#rest-style-api)
213
- */
214
- patch<T = any>(entityOrPath: types.target, data?: object): Promise<T>
215
- /**
216
- * Constructs and sends a DELETE request.
217
- */
218
- delete: {
219
- <T = any>(entityOrPath: types.target, data?: object): DELETE<T>
220
- <T extends ArrayConstructable<any>>(entity: T, key?: any): DELETE<T>
221
- <T>(entity: LinkedDefinition | string, key?: any): DELETE<T>
222
- }
223
-
224
- // The central method to dispatch events
225
- dispatch(msg: types.event): Promise<any>
226
-
227
- // Provider API
228
- prepend(fn: ServiceImpl): Promise<this>
229
- on<T extends Constructable>(eve: types.event, entity: T, handler: CRUDEventHandler.On<InstanceType<T>, InstanceType<T> | void | Error>): this
230
- on<F extends CdsFunction>(boundAction: F, service: string, handler: ActionEventHandler<F['__parameters'], void | Error | F['__returns']>): this
231
- on<F extends CdsFunction>(unboundAction: F, handler: ActionEventHandler<F['__parameters'], void | Error | F['__returns']>): this
232
- on(eve: types.event, entity: types.target, handler: OnEventHandler): this
233
- on(eve: types.event, handler: OnEventHandler): this
234
-
235
-
236
- // onSucceeded (eve: types.Events, entity: types.Target, handler: types.EventHandler): this
237
- // onSucceeded (eve: types.Events, handler: types.EventHandler): this
238
- // onFailed (eve: types.Events, entity: types.Target, handler: types.EventHandler): this
239
- // onFailed (eve: types.Events, handler: types.EventHandler): this
240
- before<T extends Constructable>(eve: types.event, entity: T, handler: CRUDEventHandler.Before<InstanceType<T>, InstanceType<T> | void | Error>): this
241
- before(eve: types.event, entity: types.target, handler: EventHandler): this
242
- before(eve: types.event, handler: EventHandler): this
243
- after<T extends Constructable>(eve: types.event, entity: T, handler: CRUDEventHandler.After<InstanceType<T>, InstanceType<T> | void | Error>): this
244
- after(eve: types.event, entity: types.target, handler: ResultsHandler): this
245
- after(eve: types.event, handler: ResultsHandler): this
246
- reject(eves: types.event, ...entity: types.target[]): this
247
- }
248
-
249
- export class ApplicationService extends Service {}
250
- export class MessagingService extends Service {}
251
- export class RemoteService extends Service {}
252
- export class DatabaseService extends Service {
253
- deploy(model?: CSN | string): Promise<CSN>
254
- begin(): Promise<void>
255
- commit(): Promise<void>
256
- rollback(): Promise<void>
257
- }
258
-
259
-
260
- export default class cds {
261
- /**
262
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/core-services)
263
- */
264
- Service: typeof Service
265
-
266
- /**
267
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/app-services)
268
- */
269
- ApplicationService: typeof ApplicationService
270
-
271
- /**
272
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/remote-services)
273
- */
274
- RemoteService: typeof RemoteService
275
-
276
- /**
277
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/messaging)
278
- */
279
- MessagingService: typeof MessagingService
280
-
281
- /**
282
- * @see [capire docs](https://cap.cloud.sap/docs/node.js/databases)
283
- */
284
- DatabaseService: typeof DatabaseService
285
- }
286
-
287
-
288
- interface Transaction extends Service {
289
- commit(): Promise<void>
290
- rollback(): Promise<void>
291
- }
292
-
293
- interface ResultSet extends Array<{}> {}
294
-
295
- interface ServiceImpl {
296
- (this: Service, srv: Service): any
297
- }
298
-
299
- interface EventHandler {
300
- // (msg : types.EventMessage) : Promise<any> | any | void
301
- (req: Request): Promise<any> | any | void
302
- }
303
-
304
- interface OnEventHandler {
305
- (req: Request, next: Function): Promise<any> | any | void
306
- }
307
-
308
- // `Partial` wraps any type and allows all properties to be undefined
309
- // in addition to their actual type.
310
- // This is important in the context of CRUD events, where
311
- // entities could be lacking any properties, like a non-existing ID
312
- // or when DB fields are corrupted, etc.
313
- type Partial<T> = { [Key in keyof T]: undefined | T[Key] }
314
-
315
- // Naming the first parameter in a handler `each` has special voodoo
316
- // semantic which makes it impossible for us to infer the exact behaviour on a type level.
317
- // So we always have to expect scalars as well as arrays in some callbacks.
318
- type OneOrMany<T> = T | T[];
319
-
320
- // functions generated by cds-typer explicitly carry types for
321
- // parameters and return type, as their names are not accessible from
322
- // function signatures to the type system.
323
- // This meta information is required in .on action handlers.
324
- type CdsFunction = {
325
- (...args: any[]): any,
326
- __parameters: object,
327
- __returns: unknown
328
- }
329
-
330
- type TypedRequest<T> = Omit<Request, 'data'> & { data: T }
331
-
332
- // https://cap.cloud.sap/docs/node.js/core-services#srv-on-before-after
333
- declare namespace CRUDEventHandler {
334
- type Before<P,R> = (req: TypedRequest<P>) => Promise<R> | R
335
- type On<P,R> = (req: TypedRequest<P>, next: (...args: any) => Promise<R> | R) => Promise<R> | R
336
- type After<P,R> = (data: undefined | P, req: TypedRequest<P>) => Promise<R> | R
337
- }
338
-
339
- // Handlers for actions try to infer the passed .data property
340
- // as strictly as possible and therefore have to remove
341
- // { data: any } (inherited EventMessage} with a more restricted
342
- // type, based on the parameters of the action.
343
- interface ActionEventHandler<P,R> {
344
- (req: Omit<Request, 'data'> & { data: P }, next: Function): Promise<R> | R
345
- }
346
-
347
- // Note: the behaviour of ResultsHandler changes based on the name of the parameter.
348
- // If the parameter in the hook is called "each", it is called once for each row in the result,
349
- // otherwise it gets called exactly one time with the entire result.
350
- // This runtime behaviour can not be described on type level
351
- // (in a way that would benefit the user).
352
- // The user will therefore receive "any" as their result/ each. If we could some day differentiate,
353
- // we may want to add a generic to ResultsHandler which is passed from the EventHandlers down below.
354
- interface ResultsHandler {
355
- (results: any[], req: Request): void
356
- (each: any, req: Request): void
357
- }
358
-
359
- interface SpawnEvents {
360
- 'succeeded': (res: any) => void
361
- 'failed': (error: any) => void
362
- 'done': () => void
363
- }
364
-
365
- declare class SpawnEventEmitter {
366
- on<U extends keyof SpawnEvents>(
367
- event: U, listener: SpawnEvents[U]
368
- ): this;
18
+ export * from '@cap-js/cds-types/apis/services'
369
19
 
370
- emit<U extends keyof SpawnEvents>(
371
- event: U, ...args: Parameters<SpawnEvents[U]>
372
- ): boolean;
373
- timer: any
374
- }
20
+ // this got moved to 'events' in 7.4
21
+ export { Request } from '@cap-js/cds-types/apis/events'
375
22
 
376
- declare namespace types {
377
- type event = eventName | eventName[]
378
- type eventName = (string & {})
379
- | 'CREATE' | 'READ' | 'UPDATE' | 'DELETE'
380
- | 'NEW' | 'EDIT' | 'PATCH' | 'SAVE'
381
- | 'GET' | 'PUT' | 'POST' | 'PATCH' | 'DELETE'
382
- | 'COMMIT' | 'ROLLBACK'
383
- type target = string | LinkedDefinition | LinkedEntity | (string | LinkedDefinition | LinkedEntity)[] | ArrayConstructable<any>
384
- }
package/bin/cds-serve.js CHANGED
@@ -31,11 +31,14 @@ const cli = {
31
31
  else if (_global.test(a)) add_global(a, _flags[a] || argv[++i])
32
32
  else throw 'Invalid option: '+ a
33
33
  }
34
+ // consistent production setting for NODE_ENV and CDS_ENV
35
+ if (process.env.NODE_ENV !== 'production') process.env.NODE_ENV = process.env.CDS_ENV?.split(',').find(p => p === 'production') || process.env.NODE_ENV
36
+ else process.env.CDS_ENV = Array.from(new Set([...process.env.CDS_ENV?.split(',') ?? [], 'production']))
34
37
 
35
38
  function add (k,v) { options[k.slice(2)] = v || true }
36
39
  function add_global (k,v='') {
37
- if (k === '--production') return process.env.NODE_ENV = 'production'
38
- if (k === '--profile') return process.env.CDS_ENV = v.split(',')
40
+ if (k === '--production') return process.env.CDS_ENV = Array.from(new Set([...process.env.CDS_ENV?.split(',') ?? [], 'production']))
41
+ if (k === '--profile') return process.env.CDS_ENV = Array.from(new Set([...process.env.CDS_ENV?.split(',') ?? [], ...v.split(',')]))
39
42
  if (k === '--odata') v = { flavor:v }
40
43
  let e=env || (env={}), path = k.slice(2).split('-')
41
44
  while (path.length > 1) { let p = path.shift(); e = e[p]||(e[p]={}) }
package/bin/serve.js CHANGED
@@ -127,7 +127,7 @@ module.exports = Object.assign ( serve, {
127
127
  `})
128
128
 
129
129
 
130
- const cds = require('../lib'), { exists, isfile, local, path } = cds.utils
130
+ const cds = require('../lib'), { exists, isfile, local, path, _redacted } = cds.utils
131
131
  const COLORS = !!process.stdout.isTTY && !!process.stderr.isTTY && !process.env.NO_COLOR
132
132
 
133
133
  // provisional loggers, see _prepare_logging
@@ -261,7 +261,12 @@ function _prepare_logging () { // NOSONAR
261
261
  // print information when model is loaded
262
262
  cds.on ('loaded', (model)=>{
263
263
  LOG.info (`loaded model from ${model.$sources.length} file(s):\n${COLORS ? '\x1b[2m' : ''}`)
264
- for (let each of model.$sources) console.log (' ', local(each))
264
+ const limit = 30, shown = model.$sources.length === limit + 1 ? limit + 1 : limit // REVISIT: configurable limit?
265
+ for (let each of model.$sources.slice(0, shown)) console.log (' ', local(each))
266
+ if (model.$sources.length > shown) {
267
+ if (LOG._debug) for (let each of model.$sources.slice(shown)) console.log (' ', local(each))
268
+ else console.log (` ...${model.$sources.length-shown} more. Run with DEBUG=serve to show all files.`)
269
+ }
265
270
  COLORS && console.log ('\x1b[0m')
266
271
  })
267
272
 
@@ -337,20 +342,6 @@ function _with_mocks (o) {
337
342
  }
338
343
  }
339
344
 
340
- const SECRETS = /(password)|(certificate)|(ca)|(secret)|(key)/i // 'certificate' and 'ca' on HANA
341
- /** mascades password-like strings, also reducing clutter in output */
342
- function _redacted(cred) {
343
- if (!cred) return cred
344
- if (Array.isArray(cred)) return cred.map(_redacted)
345
- if (typeof cred === 'object') {
346
- const newCred = Object.assign({}, cred)
347
- Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
348
- return newCred
349
- }
350
- return cred
351
- }
352
-
353
-
354
345
  const _warn_if_cds_was_loaded_from_different_locations = ()=> global.__cds_loaded_from?.size > 1 && console.warn(`
355
346
  -----------------------------------------------------------------------
356
347
  WARNING: Package '@sap/cds' was loaded from different installations:`,
@@ -12,13 +12,15 @@ module.exports = function basic_auth (options) {
12
12
  // get basic authorization header
13
13
  let auth = req.headers.authorization
14
14
  // enforce login if requested
15
- if (!auth || !auth.match(/^basic/i)) return login_required ? req._login('Logged in user required!') : req.user = new cds.User.default, next()
15
+ if (!auth?.match(/^basic/i)) return login_required ? req._login('Logged-in user required!') : next()
16
16
  // decode user credentials from autorization header
17
17
  let [id,pwd] = Buffer.from(auth.slice(6),'base64').toString().split(':')
18
18
  // verify user credentials and set req.user
19
19
  let u = req.user = await users.verify (id, pwd)
20
20
  // re-request login in case of wrong credentials
21
21
  if (u.failed) return req._login (u)
22
+ // set req.tenant
23
+ if (u.tenant) req.tenant = u.tenant
22
24
  // support for feature toggles via req.headers.features
23
25
  if (req.headers.features) u = req.user = { ...u, features: req.headers.features } // NOTE: need to clone u
24
26
  // done...
@@ -3,65 +3,79 @@ const LOG = cds.log('auth')
3
3
 
4
4
  // _require for better error message
5
5
  const _require = require('../../libx/_runtime/common/utils/require')
6
- const express = _require('express')
7
- const passport = _require('passport')
8
- const { JWTStrategy } = _require('@sap/xssec')
9
-
10
- const RESERVED_ATTRIBUTES = new Set(['aud', 'azp', 'exp', 'ext_attr', 'iat', 'ias_iss', 'iss', 'jti', 'sub', 'user_uuid', 'zone_uuid', 'zid'])
6
+ const xssec = _require('@sap/xssec')
11
7
 
12
8
  module.exports = function ias_auth(config) {
13
- if (!config.credentials) {
14
- let msg = `Authentication kind "${config.kind}" configured, but no IAS instance bound to application.`
9
+ const { kind, credentials, known_claims } = config
10
+
11
+ if (!credentials) {
12
+ let msg = `Authentication kind "${kind}" configured, but no IAS instance bound to application.`
15
13
  msg += ' Either bind an IAS instance, or switch to an authentication kind that does not require a binding.'
16
14
  throw new Error(msg)
17
15
  }
18
16
 
19
- passport.use('IAS', new JWTStrategy(config.credentials))
20
-
21
- return express
22
- .Router()
23
- .use((req, res, next) => {
24
- // callback needed in order to suppress 401 and continue with anonymous to allow @requires: 'any'/ restrict_all_services=false
25
- const callback = (err, user, info, _status) => {
26
- if (err) return next(err)
27
-
28
- if (user) {
29
- req.user = user
30
- req.authInfo = info
31
- } else {
32
- req.user = new cds.User.default
33
- if (LOG._debug && req.tokenInfo)
34
- LOG.debug('Error during token validation:', req.tokenInfo.getErrorObject().message)
17
+ // cds.env.requires.auth.known_claims is not an official config!
18
+ const KNOWN_CLAIMS = new Set(known_claims || require('./ias-claims'))
19
+
20
+ function getUser(tokenInfo) {
21
+ const payload = tokenInfo.getPayload()
22
+
23
+ const clientid = tokenInfo.getClientId()
24
+ if (clientid === payload.sub) {
25
+ //> grant_type === client_credentials or x509
26
+ const roles = ['system-user']
27
+ if (clientid === credentials.clientid) roles.push('internal-user')
28
+ return new cds.User({ id: 'system', roles })
29
+ }
30
+
31
+ // add all unknown attributes to req.user.attr in order to keep public API small
32
+ const attr = Object.keys(payload)
33
+ .filter(k => !KNOWN_CLAIMS.has(k))
34
+ .reduce((attr, k) => { attr[k] = payload[k]; return attr }, {})
35
+
36
+ // same api as xsuaa-auth for easier migration
37
+ if (attr.user_name) attr.logonName = attr.user_name
38
+ if (attr.given_name) attr.givenName = attr.given_name
39
+ if (attr.family_name) attr.familyName = attr.family_name
40
+
41
+ return new cds.User({ id: payload.sub, attr })
42
+ }
43
+
44
+ return (req, _, next) => {
45
+ const token = req.headers.authorization?.split(/^bearer /i)[1]
46
+ xssec.createSecurityContext(token, credentials, 'IAS', function (err, securityContext, tokenInfo) {
47
+ // REVISIT: ias impl not as sophisticated as xsuaa impl, so we need to be more tolerant here -> xssec issue 221
48
+ // if (err && !tokenInfo) {
49
+ // // here, there is a general problem, .e.g., bad credentials -> throw the error
50
+ // return next(err)
51
+ // }
52
+
53
+ if (err && LOG._debug) LOG.debug('User could not be authenticated due to error:', err)
54
+
55
+ // if no general problem, tokenInfo object is always available -> add to req via getter for compat reasons
56
+ // -> the "always available" part is not true for ias (see REVISIT above)
57
+ tokenInfo && Object.defineProperty(req, 'tokenInfo', {
58
+ get() {
59
+ cds._logDeprecation('req.tokenInfo was added for compatibility purposes but will be removed in the next major version.')
60
+ return tokenInfo
35
61
  }
62
+ })
36
63
 
37
- next()
38
- }
39
- passport.authenticate('IAS', { session: false }, callback)(req, res, next)
40
- })
41
- .use((req, res, next) => {
42
- if (!('authInfo' in req)) return next()
43
-
44
- const payload = req.tokenInfo.getPayload()
45
- if (req.tokenInfo.getClientId() === req.tokenInfo.getSubject()) {
46
- //> grant_type === client_credentials or x509
47
- const roles = ['authenticated-user', 'system-user']
48
- if (payload.azp === config.credentials.clientid) roles.push('internal-user')
49
- req.user = new cds.User({ id: 'system', roles, attr: {} })
50
- } else {
51
- // add all unknown attributes to req.user.attr in order to keep public API small
52
- const attributes = Object.keys(payload)
53
- .filter(k => !RESERVED_ATTRIBUTES.has(k))
54
- .reduce((attrs, k) => { attrs[k] = payload[k]; return attrs }, {})
55
-
56
- req.user = new cds.User({
57
- id: req.user.id,
58
- roles: ['authenticated-user'],
59
- attr: attributes
60
- })
64
+ if (!securityContext) {
65
+ if (!req.headers.authorization) {
66
+ LOG._debug && LOG.debug('No authorization header provided, continuing with default user.')
67
+ req.user = new cds.User.default()
68
+ return next()
69
+ }
70
+ return next(new cds.error('Unauthorized', { statusCode: 401 }))
61
71
  }
62
72
 
63
- req.tenant = req.tokenInfo.getZoneId()
73
+ req.user = getUser(tokenInfo)
74
+ req.tenant = tokenInfo.getZoneId()
75
+
76
+ req.authInfo = securityContext //> compat req.authInfo
64
77
 
65
78
  next()
66
79
  })
80
+ }
67
81
  }
@@ -0,0 +1,34 @@
1
+ module.exports = Object.values({
2
+ /*
3
+ * JWT claims (https://datatracker.ietf.org/doc/html/rfc7519#section-4)
4
+ */
5
+ ISSUER: 'iss',
6
+ SUBJECT: 'sub',
7
+ AUDIENCE: 'aud',
8
+ EXPIRATION_TIME: 'exp',
9
+ NOT_BEFORE: 'nbf',
10
+ ISSUED_AT: 'iat',
11
+ JWT_ID: 'jti',
12
+ /*
13
+ * TokenClaims (com.sap.cloud.security.token.TokenClaims)
14
+ */
15
+ // ISSUER: "iss", //> already in JWT claims
16
+ IAS_ISSUER: 'ias_iss',
17
+ // EXPIRATION: "exp", //> already in JWT claims
18
+ // AUDIENCE: "aud", //> already in JWT claims
19
+ // NOT_BEFORE: "nbf", //> already in JWT claims
20
+ // SUBJECT: "sub", //> already in JWT claims
21
+ // USER_NAME: 'user_name', //> do not exclude
22
+ // GIVEN_NAME: 'given_name', //> do not exclude
23
+ // FAMILY_NAME: 'family_name', //> do not exclude
24
+ // EMAIL: 'email', //> do not exclude
25
+ SAP_GLOBAL_SCIM_ID: 'scim_id',
26
+ SAP_GLOBAL_USER_ID: 'user_uuid', //> exclude for now
27
+ SAP_GLOBAL_ZONE_ID: 'zone_uuid',
28
+ // GROUPS: 'groups', //> do not exclude
29
+ AUTHORIZATION_PARTY: 'azp',
30
+ CNF: 'cnf',
31
+ CNF_X5T: 'x5t#S256',
32
+ // own
33
+ APP_TENANT_ID: 'app_tid'
34
+ })