@sap/cds 7.9.4 → 8.0.4

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 (276) hide show
  1. package/CHANGELOG.md +128 -3659
  2. package/_i18n/i18n_en_US_saptrc.properties +113 -0
  3. package/_i18n/i18n_zh_CN.properties +7 -4
  4. package/app/index.css +129 -0
  5. package/app/index.html +16 -64
  6. package/app/index.js +14 -9
  7. package/bin/args.js +34 -0
  8. package/bin/serve.js +18 -24
  9. package/bin/test.js +97 -0
  10. package/common.cds +5 -12
  11. package/eslint.config.mjs +133 -0
  12. package/lib/auth/basic-auth.js +16 -20
  13. package/lib/auth/dummy-auth.js +1 -1
  14. package/lib/auth/ias-auth.js +9 -41
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +10 -40
  17. package/lib/compile/cds-compile.js +1 -2
  18. package/lib/compile/cdsc.js +21 -26
  19. package/lib/compile/etc/_localized.js +1 -6
  20. package/lib/compile/etc/csv.js +1 -1
  21. package/lib/compile/etc/properties.js +1 -1
  22. package/lib/compile/for/java.js +1 -1
  23. package/lib/compile/for/lean_drafts.js +4 -6
  24. package/lib/compile/for/nodejs.js +1 -1
  25. package/lib/compile/parse.js +4 -0
  26. package/lib/compile/resolve.js +4 -4
  27. package/lib/compile/to/edm-files.js +16 -23
  28. package/lib/compile/to/hana.js +27 -0
  29. package/lib/compile/to/json.js +1 -1
  30. package/lib/compile/to/sql.js +5 -1
  31. package/lib/compile/to/yaml.js +3 -3
  32. package/lib/dbs/cds-deploy.js +4 -2
  33. package/lib/env/cds-env.js +10 -14
  34. package/lib/env/cds-requires.js +30 -13
  35. package/lib/env/defaults.js +46 -16
  36. package/lib/env/plugins.js +1 -1
  37. package/lib/env/schemas/cds-rc.js +8 -4
  38. package/lib/env/schemas/index.js +7 -7
  39. package/lib/env/serviceBindings.js +1 -1
  40. package/lib/index.js +12 -10
  41. package/lib/lazy.js +1 -1
  42. package/lib/linked/classes.js +36 -8
  43. package/lib/linked/entities.js +2 -10
  44. package/lib/linked/models.js +2 -1
  45. package/lib/linked/validate.js +292 -0
  46. package/lib/log/cds-error.js +0 -6
  47. package/lib/log/cds-log.js +3 -3
  48. package/lib/log/format/json.js +1 -1
  49. package/lib/log/service/index.js +0 -1
  50. package/lib/plugins.js +2 -2
  51. package/lib/ql/Query.js +2 -10
  52. package/lib/ql/SELECT.js +1 -1
  53. package/lib/ql/Whereable.js +3 -2
  54. package/lib/req/cds-context.js +14 -25
  55. package/lib/req/context.js +23 -25
  56. package/lib/req/request.js +1 -34
  57. package/lib/req/user.js +47 -35
  58. package/lib/srv/bindings.js +1 -1
  59. package/lib/srv/cds-connect.js +4 -4
  60. package/lib/srv/cds-serve.js +2 -2
  61. package/lib/srv/factory.js +1 -1
  62. package/lib/srv/middlewares/cds-context.js +11 -22
  63. package/lib/srv/middlewares/ctx-model.js +2 -3
  64. package/lib/srv/middlewares/errors.js +41 -8
  65. package/lib/srv/middlewares/index.js +3 -3
  66. package/lib/srv/middlewares/trace.js +0 -2
  67. package/lib/srv/protocols/hcql.js +15 -10
  68. package/lib/srv/protocols/http.js +44 -49
  69. package/lib/srv/protocols/index.js +1 -23
  70. package/lib/srv/protocols/odata-v4.js +12 -74
  71. package/lib/srv/protocols/rest.js +1 -13
  72. package/lib/srv/srv-api.js +0 -20
  73. package/lib/srv/srv-dispatch.js +3 -2
  74. package/lib/srv/srv-handlers.js +22 -11
  75. package/lib/srv/srv-methods.js +2 -2
  76. package/lib/srv/srv-models.js +3 -36
  77. package/lib/test/expect.js +343 -0
  78. package/lib/test/index.js +2 -0
  79. package/lib/test/reporter.js +176 -0
  80. package/lib/utils/axios.js +10 -9
  81. package/lib/utils/cds-test.js +85 -36
  82. package/lib/utils/cds-utils.js +54 -7
  83. package/lib/utils/check-version.js +0 -4
  84. package/lib/utils/colors.js +49 -0
  85. package/lib/utils/data.js +5 -4
  86. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  87. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  99. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  107. package/libx/_runtime/cds-services/util/assert.js +1 -1
  108. package/libx/_runtime/cds.js +10 -3
  109. package/libx/_runtime/common/Service.js +12 -32
  110. package/libx/_runtime/common/aspects/any.js +1 -0
  111. package/libx/_runtime/common/code-ext/execute.js +1 -1
  112. package/libx/_runtime/common/code-ext/worker.js +0 -1
  113. package/libx/_runtime/common/composition/data.js +0 -1
  114. package/libx/_runtime/common/composition/delete.js +0 -1
  115. package/libx/_runtime/common/composition/tree.js +0 -1
  116. package/libx/_runtime/common/composition/update.js +3 -3
  117. package/libx/_runtime/common/error/frontend.js +21 -12
  118. package/libx/_runtime/common/error/log.js +36 -0
  119. package/libx/_runtime/common/error/utils.js +2 -5
  120. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  121. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  122. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  123. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  124. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  125. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  126. package/libx/_runtime/common/generic/crud.js +6 -5
  127. package/libx/_runtime/common/generic/etag.js +7 -12
  128. package/libx/_runtime/common/generic/input.js +70 -68
  129. package/libx/_runtime/common/generic/paging.js +1 -0
  130. package/libx/_runtime/common/generic/sorting.js +1 -0
  131. package/libx/_runtime/common/generic/temporal.js +8 -2
  132. package/libx/_runtime/common/i18n/index.js +1 -1
  133. package/libx/_runtime/common/i18n/messages.properties +3 -1
  134. package/libx/_runtime/common/utils/binary.js +8 -2
  135. package/libx/_runtime/common/utils/compareJson.js +5 -1
  136. package/libx/_runtime/common/utils/copy.js +6 -11
  137. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  138. package/libx/_runtime/common/utils/differ.js +3 -6
  139. package/libx/_runtime/common/utils/keys.js +77 -18
  140. package/libx/_runtime/common/utils/postProcess.js +12 -15
  141. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  142. package/libx/_runtime/common/utils/resolveView.js +2 -3
  143. package/libx/_runtime/common/utils/restrictions.js +45 -17
  144. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  145. package/libx/_runtime/common/utils/stream.js +3 -16
  146. package/libx/_runtime/common/utils/streamProp.js +8 -18
  147. package/libx/_runtime/common/utils/structured.js +1 -1
  148. package/libx/_runtime/common/utils/ucsn.js +0 -2
  149. package/libx/_runtime/db/Service.js +0 -72
  150. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  151. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  152. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  153. package/libx/_runtime/db/generic/input.js +3 -8
  154. package/libx/_runtime/db/generic/rewrite.js +1 -0
  155. package/libx/_runtime/db/query/read.js +2 -2
  156. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  157. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  158. package/libx/_runtime/db/utils/columns.js +2 -6
  159. package/libx/_runtime/fiori/lean-draft.js +138 -56
  160. package/libx/_runtime/hana/Service.js +0 -1
  161. package/libx/_runtime/hana/driver.js +1 -1
  162. package/libx/_runtime/hana/dynatrace.js +1 -2
  163. package/libx/_runtime/hana/pool.js +11 -21
  164. package/libx/_runtime/hana/streaming.js +0 -1
  165. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  167. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  168. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  170. package/libx/_runtime/messaging/event-broker.js +54 -27
  171. package/libx/_runtime/messaging/file-based.js +3 -3
  172. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  173. package/libx/_runtime/messaging/kafka.js +2 -2
  174. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  175. package/libx/_runtime/remote/Service.js +25 -25
  176. package/libx/_runtime/remote/utils/client.js +4 -5
  177. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  178. package/libx/_runtime/remote/utils/data.js +0 -1
  179. package/libx/_runtime/sqlite/Service.js +1 -2
  180. package/libx/_runtime/ucl/Service.js +37 -78
  181. package/libx/common/assert/index.js +22 -21
  182. package/libx/common/assert/type-relaxed.js +39 -0
  183. package/libx/common/assert/utils.js +3 -2
  184. package/libx/common/assert/validation.js +3 -8
  185. package/libx/common/utils/index.js +5 -0
  186. package/libx/common/utils/path.js +51 -0
  187. package/libx/odata/ODataAdapter.js +126 -0
  188. package/libx/odata/index.js +15 -2
  189. package/libx/odata/middleware/batch.js +320 -84
  190. package/libx/odata/middleware/body-parser.js +33 -0
  191. package/libx/odata/middleware/create.js +44 -59
  192. package/libx/odata/middleware/delete.js +23 -12
  193. package/libx/odata/middleware/error.js +30 -6
  194. package/libx/odata/middleware/metadata.js +38 -26
  195. package/libx/odata/middleware/operation.js +93 -69
  196. package/libx/odata/middleware/parse.js +6 -8
  197. package/libx/odata/middleware/read.js +117 -93
  198. package/libx/odata/middleware/service-document.js +22 -19
  199. package/libx/odata/middleware/stream.js +54 -56
  200. package/libx/odata/middleware/update.js +79 -87
  201. package/libx/odata/parse/afterburner.js +191 -175
  202. package/libx/odata/parse/cqn2odata.js +5 -5
  203. package/libx/odata/parse/grammar.peggy +27 -20
  204. package/libx/odata/parse/multipartToJson.js +17 -9
  205. package/libx/odata/parse/parser.js +1 -1
  206. package/libx/odata/utils/etag.js +14 -6
  207. package/libx/odata/utils/index.js +84 -12
  208. package/libx/odata/utils/metadata.js +161 -0
  209. package/libx/odata/utils/postProcess.js +89 -0
  210. package/libx/odata/utils/readAfterWrite.js +134 -17
  211. package/libx/odata/utils/result.js +36 -142
  212. package/libx/outbox/index.js +4 -3
  213. package/libx/rest/RestAdapter.js +115 -182
  214. package/libx/rest/middleware/create.js +28 -24
  215. package/libx/rest/middleware/delete.js +7 -10
  216. package/libx/rest/middleware/error.js +26 -16
  217. package/libx/rest/middleware/operation.js +48 -41
  218. package/libx/rest/middleware/parse.js +128 -126
  219. package/libx/rest/middleware/read.js +20 -27
  220. package/libx/rest/middleware/update.js +26 -31
  221. package/package.json +17 -8
  222. package/server.js +4 -2
  223. package/apis/cds.d.ts +0 -3
  224. package/apis/core.d.ts +0 -21
  225. package/apis/cqn.d.ts +0 -18
  226. package/apis/csn.d.ts +0 -21
  227. package/apis/events.d.ts +0 -18
  228. package/apis/internal/inference.d.ts +0 -18
  229. package/apis/linked.d.ts +0 -18
  230. package/apis/log.d.ts +0 -20
  231. package/apis/models.d.ts +0 -18
  232. package/apis/ql.d.ts +0 -18
  233. package/apis/reflect.d.ts +0 -32
  234. package/apis/server.d.ts +0 -18
  235. package/apis/services.d.ts +0 -22
  236. package/bin/cds-serve.js +0 -56
  237. package/lib/compile/to/gql.js +0 -15
  238. package/lib/srv/protocols/_legacy.js +0 -44
  239. package/lib/utils/jest.js +0 -43
  240. package/libx/_runtime/auth/index.js +0 -193
  241. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  242. package/libx/_runtime/auth/strategies/basic.js +0 -20
  243. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  244. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  245. package/libx/_runtime/auth/strategies/mock.js +0 -77
  246. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  247. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  248. package/libx/_runtime/common/perf/index.js +0 -19
  249. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  250. package/libx/_runtime/fiori/draft.js +0 -2
  251. package/libx/_runtime/fiori/generic/activate.js +0 -190
  252. package/libx/_runtime/fiori/generic/before.js +0 -201
  253. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  254. package/libx/_runtime/fiori/generic/delete.js +0 -21
  255. package/libx/_runtime/fiori/generic/edit.js +0 -157
  256. package/libx/_runtime/fiori/generic/index.js +0 -25
  257. package/libx/_runtime/fiori/generic/new.js +0 -82
  258. package/libx/_runtime/fiori/generic/patch.js +0 -101
  259. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  260. package/libx/_runtime/fiori/generic/read.js +0 -1340
  261. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  262. package/libx/_runtime/fiori/utils/csn.js +0 -13
  263. package/libx/_runtime/fiori/utils/delete.js +0 -114
  264. package/libx/_runtime/fiori/utils/handler.js +0 -264
  265. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  266. package/libx/_runtime/fiori/utils/req.js +0 -23
  267. package/libx/_runtime/fiori/utils/stream.js +0 -36
  268. package/libx/_runtime/fiori/utils/where.js +0 -254
  269. package/libx/_runtime/index.js +0 -22
  270. package/libx/odata/utils/handler.js +0 -120
  271. package/libx/odata/utils/metaInfo.js +0 -410
  272. package/libx/odata/utils/path.js +0 -75
  273. package/libx/rest/RestRequest.js +0 -32
  274. package/libx/rest/index.js +0 -3
  275. package/libx/rest/readme.md +0 -1
  276. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Recommended ESLint config for @sap/cds projects.
3
+ */
4
+ export const defaults = {
5
+
6
+ rules: {
7
+ 'no-unused-vars': 'warn',
8
+ 'no-console': 'warn',
9
+ },
10
+
11
+ languageOptions: {
12
+ ecmaVersion: 2022,
13
+ globals: {
14
+
15
+ // cds.ql commands ...
16
+ SELECT: 'readonly',
17
+ INSERT: 'readonly',
18
+ UPSERT: 'readonly',
19
+ UPDATE: 'readonly',
20
+ DELETE: 'readonly',
21
+ CREATE: 'readonly',
22
+ DROP: 'readonly',
23
+
24
+ // tagged template strings ...
25
+ CDL: 'readonly',
26
+ CQL: 'readonly',
27
+ CXL: 'readonly',
28
+
29
+ // subset of Node.js globals ...
30
+ __dirname: 'readonly',
31
+ __filename: 'readonly',
32
+ exports: 'writable',
33
+ require: 'readonly',
34
+ global: 'readonly',
35
+ module: 'readonly',
36
+ console: 'readonly',
37
+ process: 'readonly',
38
+ performance: 'readonly',
39
+ setImmediate: 'readonly',
40
+ setInterval: 'readonly',
41
+ setTimeout: 'readonly',
42
+ clearImmediate: 'readonly',
43
+ clearInterval: 'readonly',
44
+ clearTimeout: 'readonly',
45
+ structuredClone: 'readonly',
46
+
47
+ Buffer: 'readonly',
48
+ fetch: 'readonly',
49
+ URL: 'readonly',
50
+ URLSearchParams: 'readonly',
51
+ }
52
+ },
53
+
54
+ linterOptions: {
55
+ reportUnusedDisableDirectives: false,
56
+ }
57
+ }
58
+
59
+ /**
60
+ * ESLint config for jest and mocha test.
61
+ */
62
+ export const tests = {
63
+ files: [ '**/test/**/*.js', '**/test?/**/*.js', '**/*.test.js', '**/*-test.js' ],
64
+ languageOptions: {
65
+ globals: {
66
+ mocha: 'readonly',
67
+ jest: 'readonly',
68
+ expect: 'readonly',
69
+ describe: 'writable',
70
+ xdescribe: 'writable',
71
+ context: 'writable',
72
+ suite: 'writable',
73
+ test: 'writable', xtest: 'writable',
74
+ it: 'writable',
75
+ fail: 'writable',
76
+ before: 'readonly',
77
+ after: 'readonly',
78
+ beforeAll: 'readonly',
79
+ afterAll: 'readonly',
80
+ beforeEach: 'readonly',
81
+ afterEach: 'readonly',
82
+ }
83
+ },
84
+ }
85
+
86
+ /**
87
+ * ESLint config for code running in web browsers.
88
+ */
89
+ export const browser = {
90
+ files: [ '**/app/**/*.js', '**/webapp/**/*.js' ],
91
+ languageOptions: {
92
+ globals: {
93
+ window: 'readonly',
94
+ history: 'readonly',
95
+ document: 'readonly',
96
+ location: 'writeable',
97
+ localStorage: 'readonly',
98
+ sessionStorage: 'readonly',
99
+ parent: 'readonly',
100
+ event: 'readonly',
101
+ sap: 'readonly',
102
+ }
103
+ },
104
+ }
105
+
106
+ /**
107
+ * Global ignores for all configs.
108
+ */
109
+ export const ignores = [
110
+ '**/@cds-models/**',
111
+ '**/node_modules/**',
112
+ 'node_modules/**',
113
+ // '**/webapp/**',
114
+ ]
115
+
116
+ /**
117
+ * Recommended all-in-one config for external eslint use, i.e. in cap/dev
118
+ * monorepo, other cap impl projects, as well as in cap-based projects.
119
+ * Currently the same as internal, could differ in the future.
120
+ */
121
+ export const recommended = [ defaults, browser, tests, {ignores} ]
122
+ await import('@eslint/js').then( // add recommended eslint rules if available
123
+ ({default:eslint}) => recommended.unshift (eslint.configs.recommended)
124
+ ).catch(()=>{})
125
+
126
+ /**
127
+ * Default export is for internal use in @sap/cds and cap/dev projects.
128
+ * It adds additional ignores, e.g. for peggy-generated parser code.
129
+ */
130
+ export default Object.assign ([ ...recommended, {ignores:[
131
+ '**/libx/odata/parse/parser.js',
132
+ '**/okra/**/*.js',
133
+ ]}], { recommended, defaults, browser, tests, ignores })
@@ -1,37 +1,33 @@
1
1
  module.exports = function basic_auth (options) {
2
2
 
3
- const cds = require ('../index'), LOG = cds.log('auth'), { decodeURIComponent } = cds.utils
3
+ const cds = require ('../index'), DEBUG = cds.debug('basic|auth')
4
4
  const users = require ('./mocked-users') (options)
5
- const login_required = options.login === 'required' || process.env.NODE_ENV === 'production' && options.credentials || cds.requires.multitenancy
5
+ const login_required = options.login_required || cds.requires.multitenancy || process.env.NODE_ENV === 'production' && options.credentials
6
6
 
7
7
  /** @type { import('express').Handler } express_handler */
8
8
  return async function basic_auth (req, res, next) {
9
- // REVISIT: passport also adds a login function with different meaning -> we need to be able to recognize ours -> req._login for now
10
- // allow subsequent code to request a user login
11
- req._login = login
12
- // get basic authorization header
13
- let auth = req.headers.authorization
9
+ req._login = login // allow subsequent code to request a user login
10
+ let auth = req.headers.authorization // get basic authorization header
14
11
  // enforce login if requested
15
- if (!auth?.match(/^basic/i)) return login_required ? req._login('Logged-in user required!') : next()
12
+ if (!auth?.match(/^basic/i)) return login_required ? req._login() : next()
16
13
  // decode user credentials from autorization header
17
14
  let [id,pwd] = Buffer.from(auth.slice(6),'base64').toString().split(':')
18
15
  // verify user credentials and set req.user
19
16
  let u = req.user = await users.verify (id, pwd)
20
17
  // re-request login in case of wrong credentials
21
- if (u.failed) return req._login (u)
22
- // set req.tenant
23
- if (u.tenant) req.tenant = u.tenant
24
- // support for feature toggles via req.headers.features
25
- if (req.headers.features) u = req.user = { ...u, features: req.headers.features } // NOTE: need to clone u
26
- // done...
27
- if (LOG._debug) LOG.debug('authenticated user:', u)
18
+ if (u.failed) return req._login()
19
+ // user authenticated...
20
+ const ctx = cds.context; ctx.user = u
21
+ const features = req.headers.features || u.features // IMPORTANT: only here not as public API
22
+ if (features) ctx.features = features
23
+ if (u.tenant) ctx.tenant = u.tenant
24
+ DEBUG?.('authenticated:', { user: u.id, tenant: u.tenant, features })
25
+ // done
28
26
  next()
29
27
  }
30
28
 
31
- function login (reason='') {
32
- const req=this, res=req.res
33
- // REVISIT: this json response is needed to be OData compliant. however, we should probably throw an error anyway so that a custom error middleware can get invoked.
34
- res.set('www-authenticate', `Basic realm="Users"`).status(401).json({ error: { code: '401', message: 'Unauthorized' } })
35
- LOG.info (req.method, decodeURIComponent(req.path), '>', res.statusCode, res.statusMessage, ...(!reason ? [] : ['-', reason]))
29
+ function login() {
30
+ DEBUG?.(401, '> login required') // REVISIT: do we really need auth checks on HEAD requests?
31
+ this.res.set('WWW-Authenticate', `Basic realm="Users"`).sendStatus(401)
36
32
  }
37
33
  }
@@ -1,4 +1,4 @@
1
- const { User: { privileged } } = require ('../index')
1
+ const cds = require ('../index'), {privileged} = cds.User
2
2
 
3
3
  module.exports = function dummy_auth() {
4
4
  return function dummy_auth (req, res, next) {
@@ -5,15 +5,8 @@ const LOG = cds.log('auth')
5
5
  const _require = require('../../libx/_runtime/common/utils/require')
6
6
 
7
7
  let xssec = _require('@sap/xssec')
8
- let xssec4 = false
9
8
  // use v3 compat api
10
- if (xssec.v3) {
11
- xssec = xssec.v3
12
- xssec4 = true
13
- }
14
-
15
- // getter function extracted to show deprecation warning only once
16
- const _getTokenInfo = tokenInfo => tokenInfo
9
+ if (xssec.v3) xssec = xssec.v3
17
10
 
18
11
  module.exports = function ias_auth(config) {
19
12
  const { kind, credentials, known_claims } = config
@@ -52,42 +45,17 @@ module.exports = function ias_auth(config) {
52
45
  }
53
46
 
54
47
  return (req, _, next) => {
48
+ if (!req.headers.authorization) return next()
49
+
55
50
  const token = req.headers.authorization?.split(/^bearer /i)[1]
51
+ xssec.createSecurityContext(token, credentials, 'IAS', function (err, securityContext, tokenInfo) {
52
+ if (err) LOG.error('User could not be authenticated due to error:', err)
56
53
 
57
- if (!token && xssec4) {
58
- LOG._debug && LOG.debug('No authorization header provided, continuing with default user.')
59
- req.user = new cds.User.default()
60
- return next()
61
- }
54
+ if (!securityContext) return next(new cds.error('Unauthorized', { code: 401 }))
62
55
 
63
- xssec.createSecurityContext(token, credentials, 'IAS', function (err, securityContext, tokenInfo) {
64
- // REVISIT: ias impl not as sophisticated as xsuaa impl, so we need to be more tolerant here -> xssec issue 221
65
- // if (err && !tokenInfo) {
66
- // // here, there is a general problem, .e.g., bad credentials -> throw the error
67
- // return next(err)
68
- // }
69
-
70
- if (err && LOG._debug) LOG.debug('User could not be authenticated due to error:', err)
71
-
72
- // if no general problem, tokenInfo object is always available -> add to req via getter for compat reasons
73
- // -> the "always available" part is not true for ias (see REVISIT above)
74
- tokenInfo && Object.defineProperty(req, 'tokenInfo', {
75
- get() {
76
- return cds.utils.deprecated(_getTokenInfo, {kind: 'Property', old: 'req.tokenInfo'})(tokenInfo)
77
- }
78
- })
79
-
80
- if (!securityContext) {
81
- if (!req.headers.authorization) {
82
- LOG._debug && LOG.debug('No authorization header provided, continuing with default user.')
83
- req.user = new cds.User.default()
84
- return next()
85
- }
86
- return next(new cds.error('Unauthorized', { statusCode: 401 }))
87
- }
88
-
89
- req.user = getUser(tokenInfo)
90
- req.tenant = tokenInfo.getZoneId()
56
+ const ctx = cds.context
57
+ ctx.user = getUser(tokenInfo)
58
+ ctx.tenant = tokenInfo.getZoneId()
91
59
 
92
60
  req.authInfo = securityContext //> compat req.authInfo
93
61
 
package/lib/auth/index.js CHANGED
@@ -46,18 +46,5 @@ module.exports = function auth_factory (o) {
46
46
  if (typeof auth === 'function' && auth.length < 3) auth = auth(options)
47
47
 
48
48
  // return the auth middleware followed by a middleware to fill in cds.context
49
- return [ auth, ctx_auth ]
49
+ return auth
50
50
  }
51
-
52
- /**
53
- * Propagate user and tenant from req to cds.context
54
- */
55
- function ctx_auth (req, res, next) {
56
- const ctx = cds.context
57
- ctx.user = req.user
58
- ctx.tenant = req.tenant || req.user?.tenant
59
- next()
60
- }
61
-
62
- // export ctx_auth for legacy protocol adapter -> remove with cds^8
63
- module.exports._ctx_auth = ctx_auth
@@ -5,15 +5,8 @@ const LOG = cds.log('auth')
5
5
  const _require = require('../../libx/_runtime/common/utils/require')
6
6
 
7
7
  let xssec = _require('@sap/xssec')
8
- let xssec4 = false
9
8
  // use v3 compat api
10
- if (xssec.v3) {
11
- xssec = xssec.v3
12
- xssec4 = true
13
- }
14
-
15
- // getter function extracted to show deprecation warning only once
16
- const _getTokenInfo = tokenInfo => tokenInfo
9
+ if (xssec.v3) xssec = xssec.v3
17
10
 
18
11
  module.exports = function jwt_auth(config) {
19
12
  const { kind, credentials } = config
@@ -54,40 +47,17 @@ module.exports = function jwt_auth(config) {
54
47
  }
55
48
 
56
49
  return (req, _, next) => {
57
- const token = req.headers.authorization?.split(/^bearer /i)[1]
58
-
59
- if (!token && xssec4) {
60
- LOG._debug && LOG.debug('No authorization header provided, continuing with default user.')
61
- req.user = new cds.User.default()
62
- return next()
63
- }
50
+ if (!req.headers.authorization) return next()
64
51
 
52
+ const token = req.headers.authorization.split(/^bearer /i)[1]
65
53
  xssec.createSecurityContext(token, credentials, function (err, securityContext, tokenInfo) {
66
- if (err && !tokenInfo) {
67
- // here, there is a general problem, .e.g., bad credentials -> throw the error
68
- return next(err)
69
- }
70
-
71
- if (err && LOG._debug) LOG.debug('User could not be authenticated due to error:', err)
72
-
73
- // if no general problem, tokenInfo object is always available -> add to req via getter for compat reasons
74
- Object.defineProperty(req, 'tokenInfo', {
75
- get() {
76
- return cds.utils.deprecated(_getTokenInfo, {kind: 'Property', old: 'req.tokenInfo'})(tokenInfo)
77
- }
78
- })
79
-
80
- if (!securityContext) {
81
- if (!req.headers.authorization) {
82
- LOG._debug && LOG.debug('No authorization header provided, continuing with default user.')
83
- req.user = new cds.User.default()
84
- return next()
85
- }
86
- return next(new cds.error('Unauthorized', { statusCode: 401 }))
87
- }
88
-
89
- req.user = getUser(tokenInfo)
90
- req.tenant = tokenInfo.getZoneId()
54
+ if (err) LOG.error('User could not be authenticated due to error:', err)
55
+
56
+ if (!securityContext) return next(new cds.error('Unauthorized', { code: 401, statusCode: 401 }))
57
+
58
+ const ctx = cds.context
59
+ ctx.user = getUser(tokenInfo)
60
+ ctx.tenant = tokenInfo.getZoneId()
91
61
 
92
62
  req.authInfo = securityContext //> compat req.authInfo
93
63
 
@@ -22,10 +22,9 @@ const compile = module.exports = Object.assign (cds_compile, {
22
22
  get sql() { return super.sql = require('./to/sql') }
23
23
  get hdbcds() { return super.hdbcds = compile.to.sql.hdbcds }
24
24
  get hdbtable() { return super.hdbtable = compile.to.sql.hdbtable }
25
+ get hana() { return super.hana = compile.to.sql.hana }
25
26
  get hdbtabledata() { return super.hdbtabledata = require('./to/hdbtabledata') }
26
27
  get serviceinfo() { return super.serviceinfo = require('./to/srvinfo') } //> REVISIT: move to CLI
27
- get gql() { return super.gql = require('./to/gql') } //> REVISIT: moved to @cap-js/graphql, remove with cds^8
28
- get graphql() { return super.graphql = require('./to/gql') } //> REVISIT: moved to @cap-js/graphql, remove with cds^8
29
28
  },
30
29
 
31
30
  })
@@ -71,33 +71,28 @@ const _options = {for: Object.assign (_options4, {
71
71
  dialect : 'sqlDialect',
72
72
  names : (o,v) => v !== 'plain' ? o.sqlMapping = v : undefined,
73
73
  })
74
- if (_conf?.impl === '@cap-js/sqlite') {
75
- o.fewerLocalizedViews = !cds.env.sql.transitive_localized_views
76
- o.betterSqliteSessionVariables = true
77
- // REVISIT: compiler only considers o.betterSqliteSessionVariables if o.sqlDialect == 'sqlite'.
78
- // Yet, dialect: 'plain' is configured in cap/sflight -> below is a dirty hack which overrides that.
79
- // We should rather have a proper way to configure the dialect in the cap/sflight project, and/or
80
- // have a better way to handle o.betterSqliteSessionVariables in compiler, independent of dialect.
81
- if (!_o?.dialect && (!cds.env.sql.dialect || cds.env.sql.dialect === 'plain')) o.sqlDialect = 'sqlite'
82
- }
83
- else if (_conf?.impl === '@cap-js/hana') {
84
- o.fewerLocalizedViews = !cds.env.sql.transitive_localized_views
85
- o.withHanaAssociations = cds.env.sql.native_hana_associations
74
+
75
+ if (!o.sqlDialect) {
76
+ let dialect = _conf.dialect || _conf.kind
77
+ if (dialect) o.sqlDialect = dialect
86
78
  }
87
- else if (!cds.db && !cds.requires.db) { // e.g. for Java
88
- o.fewerLocalizedViews = !cds.env.sql.transitive_localized_views
89
- o.withHanaAssociations = cds.env.sql.native_hana_associations
90
- if (_conf?.impl === '@cap-js/sqlite') {
91
- o.betterSqliteSessionVariables = true
92
- // REVISIT: compiler only considers o.betterSqliteSessionVariables if o.sqlDialect == 'sqlite'.
93
- // Yet, dialect: 'plain' is configured in cap/sflight -> below is a dirty hack which overrides that.
94
- // We should rather have a proper way to configure the dialect in the cap/sflight project, and/or
95
- // have a better way to handle o.betterSqliteSessionVariables in compiler, independent of dialect.
96
- if (!_o?.dialect && (!cds.env.sql.dialect || cds.env.sql.dialect === 'plain')) o.sqlDialect = 'sqlite'
97
- }
79
+
80
+ const legacy_sqlite = '@sap/cds/libx/_runtime/sqlite/Service.js'
81
+ if (_conf.impl === legacy_sqlite) {
82
+ o.betterSqliteSessionVariables = false
83
+ o.fewerLocalizedViews = false
98
84
  }
99
- // console.trace(o)
100
- if (cds.env.features.assert_integrity) o.assertIntegrityType = cds.env.features.assert_integrity.toUpperCase()
85
+
86
+ const { native_hana_associations, transitive_localized_views } = cds.env.sql
87
+ if (native_hana_associations !== undefined)
88
+ o.withHanaAssociations = native_hana_associations
89
+ if (transitive_localized_views !== undefined)
90
+ o.fewerLocalizedViews = !transitive_localized_views
91
+
92
+ const { assert_integrity } = cds.env.features
93
+ if (assert_integrity)
94
+ o.assertIntegrityType = assert_integrity.toUpperCase()
95
+
101
96
  return o
102
97
  },
103
98
 
@@ -165,4 +160,4 @@ module.exports = exports = {__proto__:compile, _options,
165
160
  compile.to.cdl // smart* functions
166
161
  ),
167
162
  },
168
- }
163
+ }
@@ -5,7 +5,7 @@ const _locales_4sql = {
5
5
  plain : env.i18n.for_sql || [],
6
6
  }
7
7
 
8
- const { _texts_entries, _localized_entries } = env.cdsc.cv2 || {}
8
+ const { _localized_entries } = env.cdsc.cv2 || {}
9
9
  const _been_here = Symbol('is _localized')
10
10
 
11
11
 
@@ -42,7 +42,6 @@ function unfold_csn (m) { // NOSONAR
42
42
 
43
43
  // only do that once per model
44
44
  if (!m || m[_been_here]) return m
45
- // eslint-disable-next-line no-console
46
45
  DEBUG && DEBUG ('unfolding csn...')
47
46
  const pass2 = []
48
47
 
@@ -60,10 +59,6 @@ function unfold_csn (m) { // NOSONAR
60
59
  // Pass 1 - add localized.<locale> entities and views
61
60
  for (const each in cds.linked(m).definitions) {
62
61
  const d = m.definitions [each]
63
- // Add <entry>_texts proxies for all <entry>.texts entities
64
- if (_texts_entries !== false && each.endsWith('.texts')) {
65
- _add_proxy4 (d, each.slice(0,-6)+'_texts')
66
- }
67
62
  // Add localized.<entry> for all entities having localized views in db
68
63
  if (_localized_entries !== false && _is_localized(d)) {
69
64
  _add_proxy4 (d,`localized.${each}`, x => pass2.push([x]))
@@ -6,7 +6,7 @@ const CSV = module.exports = { read, parse }
6
6
  function read (res) {
7
7
  try{
8
8
  return CSV.parse (readFileSync (res, 'utf-8'))
9
- } catch(e){/* ignore */}
9
+ } catch {/* ignore */}
10
10
  }
11
11
 
12
12
  function parse (csv) {
@@ -35,6 +35,6 @@ function value4(raw) {
35
35
  if (raw === 'false') return false
36
36
  if (raw === '0') return 0
37
37
  else return Number(raw) || raw
38
- .replace(/^"(.*)"$/,"$1") .replace(/^'(.*)'$/,"$1")
38
+ .replace(/''/g,"'") .replace(/^"(.*)"$/,"$1") .replace(/^'(.*)'$/,"$1")
39
39
  .replace(/\\u[\dA-F]{4}/gi, (match) => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)))
40
40
  }
@@ -29,7 +29,7 @@ module.exports = function cds_compile_for_java (csn,o) {
29
29
  const rr = d['@restrict']
30
30
  if (rr) for (let r of rr) if (r.grant && r.where) try {
31
31
  r._where = JSON.stringify (cds.parse.xpr(r.where))
32
- } catch(e){/* ignored */}
32
+ } catch {/* ignored */}
33
33
  }
34
34
  Object.defineProperty (csn, '_4java', {value:dsn})
35
35
  Object.defineProperty (dsn, '_4java', {value:dsn})
@@ -130,13 +130,11 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
130
130
 
131
131
  for (const key in newEl) {
132
132
  if (
133
- key.startsWith('@assert') ||
134
- key.startsWith('@PersonalData') ||
135
- key === '@Common.FieldControl' && newEl[key]?.['#'] === 'Mandatory' ||
136
- key === '@Common.FieldControl.Mandatory' ||
137
- key === '@FieldControl.Mandatory' ||
138
133
  key === '@mandatory' ||
139
- key === '@Core.Immutable'
134
+ key === '@Common.FieldControl' && newEl[key]?.['#'] === 'Mandatory' ||
135
+ key === '@Core.Immutable' ||
136
+ key.startsWith('@assert') ||
137
+ key.startsWith('@PersonalData')
140
138
  )
141
139
  newEl[key] = undefined
142
140
  }
@@ -9,7 +9,7 @@ module.exports = function cds_compile_for_nodejs (csn,o) {
9
9
  dsn = cds.compile.for.odata (csn,o) //> creates a partial copy -> avoid any cds.linked() before
10
10
  dsn = unfold_csn (dsn)
11
11
  dsn = cds.linked (dsn)
12
- if (cds.env.fiori.lean_draft) cds.compile.for.lean_drafts(dsn, o)
12
+ cds.compile.for.lean_drafts(dsn, o)
13
13
  Object.defineProperty (csn, '_4nodejs', {value:dsn})
14
14
  Object.defineProperty (dsn, '_4nodejs', {value:dsn})
15
15
  TRACE?.timeEnd('cds.compile 4nodejs'.padEnd(22))
@@ -11,6 +11,8 @@ const parse = module.exports = Object.assign (cds_parse, {
11
11
 
12
12
  cdl: cds_parse,
13
13
  cql: (x,o) => { try { return cdsc.parse.cql(x,undefined,{ messages:[], ...o }) } catch(e) {
14
+ // cds-compiler v5 does not put messages into `e.message` anymore; render them explicitly
15
+ e.message = !e.messages ? e.message : e.toString();
14
16
  e.message = e.message.replace('<query>.cds:',`In '${e.cql = x}' at `)
15
17
  throw e // with improved error message
16
18
  }},
@@ -33,6 +35,8 @@ const parse = module.exports = Object.assign (cds_parse, {
33
35
  if (typeof x !== 'string') throw cds.error.expected `${{x}} to be an expression string`
34
36
  if (x in keywords) return {ref:[x]}
35
37
  try { return cdsc.parse.expr(x,undefined,{ messages:[], ...o }) } catch(e) {
38
+ // cds-compiler v5 does not put messages into `e.message` anymore; render them explicitly
39
+ e.message = !e.messages ? e.message : e.toString();
36
40
  e.message = e.message.replace('<expr>.cds:1:',`In '${e.expr = x}' at `)
37
41
  throw e // with improved error message
38
42
  }
@@ -34,12 +34,12 @@ module.exports = exports = function cds_resolve (model, o={}) { // NOSONAR
34
34
  // fetch file with .cds/.csn suffix as is
35
35
  if (/\.(csn|cds)$/.test(id)) try {
36
36
  return cached[id] = _resolved ([ _resolve (id,context) ])
37
- } catch(e) {/* ignored */}
37
+ } catch {/* ignored */}
38
38
 
39
39
  // try to resolve file with one of the suffixes
40
40
  for (let tail of o.suffixes || suffixes) try {
41
41
  return cached[id] = _resolved ([ _resolve (id+tail,context) ])
42
- } catch(e) {/* ignored */}
42
+ } catch {/* ignored */}
43
43
 
44
44
  // fetch all in a directory
45
45
  if (o.all !== false) try {
@@ -51,12 +51,12 @@ module.exports = exports = function cds_resolve (model, o={}) { // NOSONAR
51
51
  unique[f.slice(0,-4)] || all.push (join(local,f))
52
52
  }
53
53
  return cached[id] = _resolved (all)
54
- } catch(e) {/* ignored */}
54
+ } catch {/* ignored */}
55
55
 
56
56
  // fetch file without suffix
57
57
  if (o.any !== false && !id.endsWith('/')) try { // NOTE: this also finds .js files!
58
58
  return cached[id] = _resolved ([ _resolve (id,context) ])
59
- } catch(e) {/* ignored */}
59
+ } catch {/* ignored */}
60
60
 
61
61
  }
62
62
 
@@ -23,7 +23,6 @@ if (isMainThread) {
23
23
  const GENERATE = _generate_using_workers // for running in worker threads
24
24
  // const GENERATE = _generate_edmxs // for running in main thread
25
25
 
26
- // eslint-disable-next-line no-inner-declarations
27
26
  async function _generate_using_workers (workerData) {
28
27
  await new Promise((resolve, reject) => new Worker (__filename, { workerData })
29
28
  .on('error', reject)
@@ -35,7 +34,6 @@ if (isMainThread) {
35
34
  exports.get = _read_generated_edmx4
36
35
  }
37
36
 
38
- // eslint-disable-next-line no-inner-declarations
39
37
  function _read_generated_edmx4 (srv, kind='edmx', { tenant, features }={}) {
40
38
  let dir = path.join (OUT, tenant||'', features||'')
41
39
  let file = path.join (dir, srv.definition.name+'.'+kind)
@@ -64,32 +62,27 @@ async function _generate_edmxs ({ csn, dir, services }) {
64
62
  const { mkdir, writeFile } = cds.utils.fs.promises
65
63
  await mkdir (dir, { recursive: true })
66
64
  const cdsc = cds.compiler
67
- const promises = []
65
+ const todos = []
68
66
 
69
67
  TRACE?.time(`cdsc.generate edmxs`.padEnd(22))
70
68
 
71
- // call cdsc.to.odata to generate edm/xs
72
- let odataVersion = cds.env.odata.version
73
- let suffixes = { edmx: '.edmx' }
74
- let compile = cdsc.to.edmx // default is edmx only
75
- if (!cds.env.features.odata_new_adapter) {
76
- compile = cdsc.to.odata // edmx and edm.json
77
- suffixes.edm = '.edm.json'
78
- }
79
-
80
- let result = compile.all (csn, { serviceNames: services, messages:[] })
81
-
82
- // write edmx files to disk
83
- for (let [name,x] of Object.entries(result[odataVersion])) {
84
- for (let suffix in suffixes) {
85
- let content = suffix === 'edmx'? x[suffix] : JSON.stringify (x[suffix], minify);
86
- let file = path.join (dir, name + suffixes[suffix])
87
- let p = writeFile (file, content)
88
- .then (() => parentPort?.postMessage ({ generated: local(file) }))
89
- promises.push(p)
69
+ if (cds.env.features.odata_new_adapter) { // generate .edmx files only
70
+ let result = cdsc.to.edmx.all (csn, { serviceNames: services, messages:[] })
71
+ for (let [name,edmx] of Object.entries(result)) {
72
+ todos.push ({ file: name + '.edmx', content: edmx })
73
+ }
74
+ } else { // generate .edmx files + .edm.json files in addition
75
+ let result = cdsc.to.odata.all (csn, { serviceNames: services, messages:[] })
76
+ if (cds.env.odata.version in result) result = result[cds.env.odata.version]
77
+ for (let [name,x] of Object.entries(result)) {
78
+ todos.push ({ file: name + '.edmx', content: x.edmx })
79
+ todos.push ({ file: name + '.edm.json', content: JSON.stringify (x.edm, minify) })
90
80
  }
91
81
  }
92
- await Promise.all (promises)
82
+
83
+ await Promise.all (todos.map (({file,content}) => writeFile (path.join(dir,file), content)
84
+ .then (() => parentPort?.postMessage ({ generated: local(file) }))
85
+ ))
93
86
  TRACE?.timeEnd(`cdsc.generate edmxs`.padEnd(22))
94
87
  return true
95
88
  }
@@ -0,0 +1,27 @@
1
+ const cds = require('../..')
2
+ const cdsc = require('../cdsc')
3
+
4
+ module.exports = (csn, o, beforeCsn) => {
5
+ if (typeof beforeCsn === 'string') beforeCsn = JSON.parse(beforeCsn)
6
+
7
+ const { definitions, deletions, migrations, afterImage } = cdsc.to.hdi.migration(cds.minify(csn), o, beforeCsn)
8
+ let migrationResult = false
9
+ if (beforeCsn || Object.values(afterImage.definitions).some(def => def['@cds.persistence.journal'])) {
10
+ migrationResult = true
11
+ }
12
+
13
+ return (function* () {
14
+ for (const { name, suffix, sql } of definitions) {
15
+ yield [sql, { file: name + suffix }]
16
+ }
17
+ if (deletions.length > 0) {
18
+ yield [deletions, { file: 'deletions.json' }]
19
+ }
20
+ if (migrations.length > 0) {
21
+ yield [migrations, { file: 'migrations.json' }]
22
+ }
23
+ if (migrationResult) {
24
+ yield [afterImage, { file: 'afterImage.json' }]
25
+ }
26
+ })()
27
+ }