@voxgig/sdkgen 4.19.1 → 4.20.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/bin/voxgig-sdkgen +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +6 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +4 -1
- package/dist/utility.js +60 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +10 -0
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +15 -0
- package/project/.sdk/src/cmp/c/Main_c.ts +15 -0
- package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +371 -0
- package/project/.sdk/src/cmp/clojure/Config_clojure.ts +47 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +11 -1
- package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +374 -0
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +40 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +17 -0
- package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +441 -0
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +42 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +18 -0
- package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +392 -0
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +36 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +15 -0
- package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +418 -0
- package/project/.sdk/src/cmp/go/Config_go.ts +12 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +14 -0
- package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +322 -0
- package/project/.sdk/src/cmp/java/Config_java.ts +20 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +14 -0
- package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +298 -0
- package/project/.sdk/src/cmp/js/Config_js.ts +12 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +6 -0
- package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +244 -0
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +23 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +20 -0
- package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +440 -0
- package/project/.sdk/src/cmp/lua/Config_lua.ts +15 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +11 -0
- package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +364 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +31 -1
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +19 -0
- package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +454 -0
- package/project/.sdk/src/cmp/perl/Config_perl.ts +30 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +14 -0
- package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +387 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +12 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +7 -0
- package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +325 -0
- package/project/.sdk/src/cmp/py/Config_py.ts +12 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +12 -0
- package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +389 -0
- package/project/.sdk/src/cmp/rb/Config_rb.ts +18 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +10 -0
- package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +305 -0
- package/project/.sdk/src/cmp/rust/Config_rust.ts +41 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +16 -0
- package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +410 -0
- package/project/.sdk/src/cmp/scala/Config_scala.ts +21 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +14 -0
- package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +415 -0
- package/project/.sdk/src/cmp/swift/Config_swift.ts +37 -1
- package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
- package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +439 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +12 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -0
- package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +237 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +33 -1
- package/project/.sdk/src/cmp/zig/Main_zig.ts +16 -0
- package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +545 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +31 -20
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +36 -40
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +11 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -32
- package/project/.sdk/tm/java/utility/MakeOptions.java +13 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +14 -1
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +13 -46
- package/project/.sdk/tm/ocaml/Makefile +11 -1
- package/project/.sdk/tm/ocaml/sdk_helpers.ml +15 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +31 -25
- package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +125 -12
- package/project/.sdk/tm/ocaml/test/t_pipeline.ml +64 -6
- package/project/.sdk/tm/perl/utility/make_options.pm +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +8 -1
- package/project/.sdk/tm/rust/utility/make_options.rs +13 -1
- package/project/.sdk/tm/scala/utility/Make.scala +14 -1
- package/project/.sdk/tm/scala/utility/Prepare.scala +16 -40
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +30 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +21 -39
- package/project/.sdk/tm/zig/core/utility.zig +37 -45
- package/project/.sdk/tm/zig/test/pipeline_test.zig +34 -0
- package/project/sdkgen-package.json +1 -1
- package/src/sdkgen.ts +4 -1
- package/src/utility.ts +75 -0
- package/project/.sdk/tm/c/utility/prepare_auth.c +0 -65
- package/project/.sdk/tm/csharp/utility/PrepareAuth.cs +0 -54
- package/project/.sdk/tm/go/utility/prepare_auth.go +0 -59
- package/project/.sdk/tm/java/utility/PrepareAuth.java +0 -66
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +0 -49
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +0 -52
- package/project/.sdk/tm/perl/utility/prepare_auth.pm +0 -56
- package/project/.sdk/tm/php/utility/PrepareAuth.php +0 -45
- package/project/.sdk/tm/py/pkg/utility/prepare_auth.py +0 -46
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +0 -35
- package/project/.sdk/tm/rust/utility/prepare_auth.rs +0 -65
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +0 -78
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
Content,
|
|
4
|
+
File,
|
|
5
|
+
Folder,
|
|
6
|
+
cmp,
|
|
7
|
+
isHttpBasicAuth,
|
|
8
|
+
resolveAuthIn,
|
|
9
|
+
resolveAuthName,
|
|
10
|
+
resolveAuthPrefix,
|
|
11
|
+
} from '@voxgig/sdkgen'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
KIT,
|
|
16
|
+
getModelPath,
|
|
17
|
+
} from '@voxgig/apidef'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
|
|
21
|
+
// rather than templated. The py port of cmp/ts/PrepareAuth_ts.ts; see that
|
|
22
|
+
// file for the full account.
|
|
23
|
+
//
|
|
24
|
+
// This was a static file at `tm/py/pkg/utility/prepare_auth.py` that
|
|
25
|
+
// hardcoded a lowercase `authorization` header. apidef has always resolved
|
|
26
|
+
// the scheme's `in` and `name` into `main.kit.info.security` — joplin's
|
|
27
|
+
// says `in: "query", name: "token"` — and generation dropped both, so the
|
|
28
|
+
// SDK sent a header the API does not read and never sent the query
|
|
29
|
+
// parameter it does.
|
|
30
|
+
//
|
|
31
|
+
// A template cannot fix this, because the three placements need three
|
|
32
|
+
// different bodies and a template has to pick one. A component emits the
|
|
33
|
+
// branch this API actually uses and nothing else — no dead query code in a
|
|
34
|
+
// bearer-token SDK, and no runtime `if` on a value that is fixed at
|
|
35
|
+
// generation time.
|
|
36
|
+
const PrepareAuth = cmp(async function PrepareAuth(props: any) {
|
|
37
|
+
const { target } = props
|
|
38
|
+
const { model } = props.ctx$
|
|
39
|
+
|
|
40
|
+
// FOLDER NESTING. Main_py already opened the ONE package folder
|
|
41
|
+
// (`<name>_sdk`) that everything the SDK owns lives in, and the blanket
|
|
42
|
+
// `Copy({from: 'tm/py/pkg'})` that used to bring this file in runs inside
|
|
43
|
+
// that same folder — which is why the template's own path,
|
|
44
|
+
// `tm/py/pkg/utility/prepare_auth.py`, lands at
|
|
45
|
+
// `<name>_sdk/utility/prepare_auth.py`. So this component opens exactly
|
|
46
|
+
// ONE segment, `utility`, to reproduce that path. Opening `<name>_sdk`
|
|
47
|
+
// again would write to `<name>_sdk/<name>_sdk/utility/`, which nothing
|
|
48
|
+
// imports, while register.py kept importing the stale copy.
|
|
49
|
+
Folder({ name: 'utility' }, () => {
|
|
50
|
+
File({ name: 'prepare_auth.' + target.ext }, () => {
|
|
51
|
+
Content(render({
|
|
52
|
+
// The generated package name, spelled the way Config_py spells it.
|
|
53
|
+
// A Copy applies ctx$.stdrep to the file it copies; Content does
|
|
54
|
+
// not, so the `projectname_sdk` placeholder the template carried
|
|
55
|
+
// has to be resolved here.
|
|
56
|
+
pkg: model.const.Name.toLowerCase() + '_sdk',
|
|
57
|
+
Name: model.const.Name,
|
|
58
|
+
active: isAuthActive_py(model),
|
|
59
|
+
where: resolveAuthIn(model),
|
|
60
|
+
name: resolveAuthName(model),
|
|
61
|
+
basic: isHttpBasicAuth(model),
|
|
62
|
+
// Read so the resolution is visible at generation time even though
|
|
63
|
+
// the emitted code takes the prefix from options at runtime (the
|
|
64
|
+
// secrets feature rewrites it there).
|
|
65
|
+
prefix: resolveAuthPrefix(model),
|
|
66
|
+
}))
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
type AuthSpec = {
|
|
73
|
+
pkg: string
|
|
74
|
+
Name: string
|
|
75
|
+
active: boolean
|
|
76
|
+
where: string
|
|
77
|
+
name: string
|
|
78
|
+
basic: boolean
|
|
79
|
+
prefix: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
function render(spec: AuthSpec): string {
|
|
84
|
+
const head = `# ${spec.Name} SDK utility: prepare_auth
|
|
85
|
+
|
|
86
|
+
from __future__ import annotations
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
// NO AUTH AT ALL - the project switched it off (see isAuthActive_py for
|
|
90
|
+
// why only an EXPLICIT switch counts). The SDK gets a prepare_auth that is
|
|
91
|
+
// honest about it rather than one that pops a header nobody set;
|
|
92
|
+
// voxgig_struct is not imported, because nothing here reads an option.
|
|
93
|
+
if (!spec.active) {
|
|
94
|
+
return head + `
|
|
95
|
+
|
|
96
|
+
# This SDK is configured with authentication off, so there is no credential
|
|
97
|
+
# to place. The function stays in the pipeline because make_spec calls it
|
|
98
|
+
# unconditionally.
|
|
99
|
+
def prepare_auth_util(ctx):
|
|
100
|
+
spec = ctx.spec
|
|
101
|
+
if spec is None:
|
|
102
|
+
return None, ctx.make_error("auth_no_spec",
|
|
103
|
+
"Expected context spec property to be defined.")
|
|
104
|
+
|
|
105
|
+
return spec, None
|
|
106
|
+
`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ('query' === spec.where) return renderQuery(spec, head)
|
|
110
|
+
if ('cookie' === spec.where) return renderCookie(spec, head)
|
|
111
|
+
|
|
112
|
+
return renderHeader(spec, head)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// HEADER. Byte-for-byte the old template when the scheme resolves to the
|
|
117
|
+
// defaults (header / Authorization), so every header-based SDK regenerates
|
|
118
|
+
// unchanged — only the constant's VALUE moves with the model, plus the
|
|
119
|
+
// HTTP Basic block, which is emitted only for a basic scheme.
|
|
120
|
+
function renderHeader(spec: AuthSpec, head: string): string {
|
|
121
|
+
return head + (spec.basic ? `import base64
|
|
122
|
+
` : '') + `from ${spec.pkg}.utility.voxgig_struct import voxgig_struct as vs
|
|
123
|
+
|
|
124
|
+
HEADER_AUTH = ${pystr(headerName(spec.name))}
|
|
125
|
+
OPTION_APIKEY = "apikey"
|
|
126
|
+
` + (spec.basic ? `OPTION_SECRET = "secret"
|
|
127
|
+
` : '') + `NOT_FOUND = "__NOTFOUND__"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def prepare_auth_util(ctx):
|
|
131
|
+
spec = ctx.spec
|
|
132
|
+
if spec is None:
|
|
133
|
+
return None, ctx.make_error("auth_no_spec",
|
|
134
|
+
"Expected context spec property to be defined.")
|
|
135
|
+
|
|
136
|
+
headers = spec.headers
|
|
137
|
+
options = ctx.client.options_map()
|
|
138
|
+
|
|
139
|
+
# Public APIs that need no auth omit the options.auth block entirely.
|
|
140
|
+
if options.get("auth") is None:
|
|
141
|
+
headers.pop(HEADER_AUTH, None)
|
|
142
|
+
return spec, None
|
|
143
|
+
|
|
144
|
+
apikey = vs.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
145
|
+
` + basicBlock(spec) + `
|
|
146
|
+
if (
|
|
147
|
+
(isinstance(apikey, str) and apikey == NOT_FOUND)
|
|
148
|
+
or apikey is None
|
|
149
|
+
or apikey == ""
|
|
150
|
+
):
|
|
151
|
+
headers.pop(HEADER_AUTH, None)
|
|
152
|
+
else:
|
|
153
|
+
auth_prefix = ""
|
|
154
|
+
ap = vs.getpath(options, "auth.prefix")
|
|
155
|
+
if isinstance(ap, str):
|
|
156
|
+
auth_prefix = ap
|
|
157
|
+
apikey_val = ""
|
|
158
|
+
if isinstance(apikey, str):
|
|
159
|
+
apikey_val = apikey
|
|
160
|
+
# Empty prefix (raw apiKey credential) must not add a leading space.
|
|
161
|
+
headers[HEADER_AUTH] = (
|
|
162
|
+
auth_prefix + " " + apikey_val if auth_prefix else apikey_val
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return spec, None
|
|
166
|
+
`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
// HTTP Basic is header-only by definition: the scheme is
|
|
171
|
+
// `Authorization: Basic base64(user:pass)`. It cannot be expressed as a
|
|
172
|
+
// query parameter or a cookie, so the branch is emitted only where it can
|
|
173
|
+
// mean something — and only when the model says the scheme IS basic, so an
|
|
174
|
+
// ordinary bearer SDK carries no dead code.
|
|
175
|
+
function basicBlock(spec: AuthSpec): string {
|
|
176
|
+
if (!spec.basic) return ''
|
|
177
|
+
|
|
178
|
+
return `
|
|
179
|
+
# True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
180
|
+
# token in the header (the branch below) can never authenticate against
|
|
181
|
+
# an API that actually checks \`Authorization: Basic base64(user:pass)\`.
|
|
182
|
+
if vs.getpath(options, "auth.basic") is True:
|
|
183
|
+
secret = vs.getprop(options, OPTION_SECRET, NOT_FOUND)
|
|
184
|
+
no_apikey = (
|
|
185
|
+
(isinstance(apikey, str) and apikey == NOT_FOUND)
|
|
186
|
+
or apikey is None
|
|
187
|
+
or apikey == ""
|
|
188
|
+
)
|
|
189
|
+
no_secret = (
|
|
190
|
+
(isinstance(secret, str) and secret == NOT_FOUND)
|
|
191
|
+
or secret is None
|
|
192
|
+
or secret == ""
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if no_apikey or no_secret:
|
|
196
|
+
headers.pop(HEADER_AUTH, None)
|
|
197
|
+
else:
|
|
198
|
+
auth_prefix = ""
|
|
199
|
+
ap = vs.getpath(options, "auth.prefix")
|
|
200
|
+
if isinstance(ap, str):
|
|
201
|
+
auth_prefix = ap
|
|
202
|
+
b64 = base64.b64encode(
|
|
203
|
+
(str(apikey) + ":" + str(secret)).encode("utf-8")
|
|
204
|
+
).decode("ascii")
|
|
205
|
+
headers[HEADER_AUTH] = (
|
|
206
|
+
auth_prefix + " " + b64 if auth_prefix else b64
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return spec, None
|
|
210
|
+
`
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
// QUERY. The credential is a query parameter, so it goes in spec.query and
|
|
215
|
+
// the headers are never touched.
|
|
216
|
+
function renderQuery(spec: AuthSpec, head: string): string {
|
|
217
|
+
return head + `from ${spec.pkg}.utility.voxgig_struct import voxgig_struct as vs
|
|
218
|
+
|
|
219
|
+
QUERY_AUTH = ${pystr(spec.name)}
|
|
220
|
+
OPTION_APIKEY = "apikey"
|
|
221
|
+
NOT_FOUND = "__NOTFOUND__"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def prepare_auth_util(ctx):
|
|
225
|
+
spec = ctx.spec
|
|
226
|
+
if spec is None:
|
|
227
|
+
return None, ctx.make_error("auth_no_spec",
|
|
228
|
+
"Expected context spec property to be defined.")
|
|
229
|
+
|
|
230
|
+
query = spec.query
|
|
231
|
+
options = ctx.client.options_map()
|
|
232
|
+
|
|
233
|
+
# Public APIs that need no auth omit the options.auth block entirely.
|
|
234
|
+
if options.get("auth") is None:
|
|
235
|
+
query.pop(QUERY_AUTH, None)
|
|
236
|
+
return spec, None
|
|
237
|
+
|
|
238
|
+
apikey = vs.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
(isinstance(apikey, str) and apikey == NOT_FOUND)
|
|
242
|
+
or apikey is None
|
|
243
|
+
or apikey == ""
|
|
244
|
+
):
|
|
245
|
+
query.pop(QUERY_AUTH, None)
|
|
246
|
+
else:
|
|
247
|
+
apikey_val = ""
|
|
248
|
+
if isinstance(apikey, str):
|
|
249
|
+
apikey_val = apikey
|
|
250
|
+
# NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
|
|
251
|
+
# thing any API reads: the prefix is a header convention, so it is
|
|
252
|
+
# dropped here deliberately rather than silently concatenated.
|
|
253
|
+
query[QUERY_AUTH] = apikey_val
|
|
254
|
+
|
|
255
|
+
return spec, None
|
|
256
|
+
`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
// COOKIE. A cookie IS a header, so the credential rides the header bag -
|
|
261
|
+
// but the `cookie` header is SHARED with whatever cookies the caller set,
|
|
262
|
+
// so the pair is spliced in and out rather than the header assigned over.
|
|
263
|
+
function renderCookie(spec: AuthSpec, head: string): string {
|
|
264
|
+
return head + `from ${spec.pkg}.utility.voxgig_struct import voxgig_struct as vs
|
|
265
|
+
|
|
266
|
+
COOKIE_HEADER = "cookie"
|
|
267
|
+
COOKIE_AUTH = ${pystr(spec.name)}
|
|
268
|
+
OPTION_APIKEY = "apikey"
|
|
269
|
+
NOT_FOUND = "__NOTFOUND__"
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _cookies_without_cred(headers):
|
|
273
|
+
"""The cookie header minus our own pair, every other cookie untouched."""
|
|
274
|
+
existing = headers.get(COOKIE_HEADER)
|
|
275
|
+
if not isinstance(existing, str) or existing == "":
|
|
276
|
+
return ""
|
|
277
|
+
|
|
278
|
+
kept = []
|
|
279
|
+
for part in existing.split(";"):
|
|
280
|
+
piece = part.strip()
|
|
281
|
+
if piece == "" or piece == COOKIE_AUTH or piece.startswith(COOKIE_AUTH + "="):
|
|
282
|
+
continue
|
|
283
|
+
kept.append(piece)
|
|
284
|
+
|
|
285
|
+
return "; ".join(kept)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _apply_cookie(headers, value):
|
|
289
|
+
"""Set (value) or remove (None) our pair, leaving the rest in place.
|
|
290
|
+
|
|
291
|
+
Splicing rather than assigning also makes this idempotent: a retried
|
|
292
|
+
request cannot end up with the credential in the header twice.
|
|
293
|
+
"""
|
|
294
|
+
rest = _cookies_without_cred(headers)
|
|
295
|
+
|
|
296
|
+
if value is None:
|
|
297
|
+
if rest == "":
|
|
298
|
+
headers.pop(COOKIE_HEADER, None)
|
|
299
|
+
else:
|
|
300
|
+
headers[COOKIE_HEADER] = rest
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
pair = COOKIE_AUTH + "=" + value
|
|
304
|
+
headers[COOKIE_HEADER] = rest + "; " + pair if rest else pair
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def prepare_auth_util(ctx):
|
|
308
|
+
spec = ctx.spec
|
|
309
|
+
if spec is None:
|
|
310
|
+
return None, ctx.make_error("auth_no_spec",
|
|
311
|
+
"Expected context spec property to be defined.")
|
|
312
|
+
|
|
313
|
+
headers = spec.headers
|
|
314
|
+
options = ctx.client.options_map()
|
|
315
|
+
|
|
316
|
+
# Public APIs that need no auth omit the options.auth block entirely.
|
|
317
|
+
if options.get("auth") is None:
|
|
318
|
+
_apply_cookie(headers, None)
|
|
319
|
+
return spec, None
|
|
320
|
+
|
|
321
|
+
apikey = vs.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
322
|
+
|
|
323
|
+
if (
|
|
324
|
+
(isinstance(apikey, str) and apikey == NOT_FOUND)
|
|
325
|
+
or apikey is None
|
|
326
|
+
or apikey == ""
|
|
327
|
+
):
|
|
328
|
+
_apply_cookie(headers, None)
|
|
329
|
+
else:
|
|
330
|
+
apikey_val = ""
|
|
331
|
+
if isinstance(apikey, str):
|
|
332
|
+
apikey_val = apikey
|
|
333
|
+
# NO PREFIX IN A COOKIE either - a cookie carries a bare
|
|
334
|
+
# \`name=value\` pair, not a header's scheme-prefixed credential.
|
|
335
|
+
_apply_cookie(headers, apikey_val)
|
|
336
|
+
|
|
337
|
+
return spec, None
|
|
338
|
+
`
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
// NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING. That helper is
|
|
343
|
+
// false whenever the SPEC declares no security scheme (`main.kit.info.auth:
|
|
344
|
+
// false`) — which is a statement about the DEFINITION, not a ban on ever
|
|
345
|
+
// sending a credential. apidef writes it for every spec with no
|
|
346
|
+
// securitySchemes block, GitHub's official OpenAPI included, and those SDKs
|
|
347
|
+
// are still expected to honour an `apikey` the caller passes; the old
|
|
348
|
+
// template placed the credential unconditionally, so they did.
|
|
349
|
+
//
|
|
350
|
+
// Gating the body on `isAuthActive` therefore does not just trim dead code,
|
|
351
|
+
// it removes working authentication from every such SDK. The generated
|
|
352
|
+
// suite catches it: generatedcompile's `py: auth null beats an explicit
|
|
353
|
+
// apikey` lane generates against a fixture that declares
|
|
354
|
+
// `main: kit: info: { ... auth: false }` and then requires
|
|
355
|
+
// `wire({apikey: 'OPTKEY01'})` to reach the transport as an authorization
|
|
356
|
+
// header. With the wider gate it reports
|
|
357
|
+
//
|
|
358
|
+
// FAIL: baseline broken: an ordinary apikey was not sent:
|
|
359
|
+
// {'called': True, 'had': False, 'val': None}
|
|
360
|
+
//
|
|
361
|
+
// So the no-op is emitted only when the PROJECT says so — `config.auth.active:
|
|
362
|
+
// false`, an explicit per-SDK switch nobody sets by accident. A spec that is
|
|
363
|
+
// merely silent keeps the credential path it has always had.
|
|
364
|
+
function isAuthActive_py(model: any): boolean {
|
|
365
|
+
const auth = getModelPath(model, `main.${KIT}.config.auth`,
|
|
366
|
+
{ only_active: false, required: false })
|
|
367
|
+
return !(null != auth && false === auth.active)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
// HTTP header names are case-insensitive, and the whole py runtime spells
|
|
372
|
+
// them lowercase - the debug feature's redact list, the secrets feature's
|
|
373
|
+
// in-place rewrite of `fetchdef["headers"]["authorization"]`, and the
|
|
374
|
+
// generated tests all match on the lowercase key. Lowercasing here keeps
|
|
375
|
+
// the default byte-identical to the old template AND keeps a custom header
|
|
376
|
+
// name (`X-API-Key`) findable by all of them.
|
|
377
|
+
function headerName(name: string): string {
|
|
378
|
+
return String(name).toLowerCase()
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
function pystr(s: string): string {
|
|
383
|
+
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
export {
|
|
388
|
+
PrepareAuth
|
|
389
|
+
}
|
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
each,
|
|
14
14
|
isAuthActive,
|
|
15
15
|
isConfigData,
|
|
16
|
+
isHttpBasicAuth,
|
|
16
17
|
rawStringLiteral,
|
|
18
|
+
resolveAuthIn,
|
|
19
|
+
resolveAuthName,
|
|
17
20
|
resolveAuthPrefix,
|
|
18
21
|
serverVariables,
|
|
19
22
|
targetFeatures,
|
|
@@ -118,6 +121,17 @@ const Config = cmp(async function Config(props: any) {
|
|
|
118
121
|
const authActive = isAuthActive(model)
|
|
119
122
|
// config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
|
|
120
123
|
const authPrefix = resolveAuthPrefix(model)
|
|
124
|
+
// `in` and `name` travel with the prefix now. They were resolved by
|
|
125
|
+
// apidef all along and dropped here, so an apiKey-in-query API got an
|
|
126
|
+
// `authorization` header it does not read. `basic` joins them because
|
|
127
|
+
// PrepareAuth_rb only emits its HTTP Basic branch for a basic scheme, and
|
|
128
|
+
// that branch reads this option at runtime.
|
|
129
|
+
//
|
|
130
|
+
// Each is emitted ONLY when it differs from the default, so an SDK with a
|
|
131
|
+
// plain header/Authorization bearer scheme regenerates byte-identical.
|
|
132
|
+
const authBasic = isHttpBasicAuth(model)
|
|
133
|
+
const authIn = resolveAuthIn(model)
|
|
134
|
+
const authName = resolveAuthName(model)
|
|
121
135
|
|
|
122
136
|
let baseUrl = ''
|
|
123
137
|
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
@@ -134,7 +148,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
134
148
|
|
|
135
149
|
const authBlock = authActive
|
|
136
150
|
? ` "auth" => {
|
|
137
|
-
"prefix" => "${authPrefix}"
|
|
151
|
+
"prefix" => "${authPrefix}",${authBasic ? `
|
|
152
|
+
"basic" => true,` : ''}${'header' === authIn ? '' : `
|
|
153
|
+
"in" => "${authIn}",`}${'Authorization' === authName ? '' : `
|
|
154
|
+
"name" => "${authName}",`}
|
|
138
155
|
},\n`
|
|
139
156
|
: ''
|
|
140
157
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
|
|
24
24
|
import { Package } from './Package_rb'
|
|
25
25
|
import { Config } from './Config_rb'
|
|
26
|
+
import { PrepareAuth } from './PrepareAuth_rb'
|
|
26
27
|
import { Gitignore } from './Gitignore_rb'
|
|
27
28
|
import { MainEntity } from './MainEntity_rb'
|
|
28
29
|
import { EntityTypes } from './EntityTypes_rb'
|
|
@@ -96,6 +97,15 @@ utility.feature_hook.call(@_rootctx, "${name}")
|
|
|
96
97
|
Config({ target })
|
|
97
98
|
})
|
|
98
99
|
|
|
100
|
+
// GENERATED, NOT COPIED. Where the credential goes is a fact about the
|
|
101
|
+
// API, and tm/ can only hold one answer. See PrepareAuth_rb.
|
|
102
|
+
//
|
|
103
|
+
// At the TARGET ROOT, not inside the `.` folder above and not inside any
|
|
104
|
+
// `src`: rb's tree is flat (utility/, core/, feature/ sit at the root),
|
|
105
|
+
// the component opens the one `utility` folder it needs, and that
|
|
106
|
+
// reproduces tm/rb/utility/prepare_auth.rb's old path exactly.
|
|
107
|
+
PrepareAuth({ target })
|
|
108
|
+
|
|
99
109
|
// Generate typed models (<Sdk>_types.rb) — required by the main SDK file.
|
|
100
110
|
EntityTypes({ target })
|
|
101
111
|
|