@voxgig/sdkgen 4.19.0 → 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/cmp/AgentGuideContent.js +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +6 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/testkit.js +2 -2
- package/dist/testkit.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/model/feature/feature-index.aon +20 -20
- 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/cmp/AgentGuideContent.ts +1 -1
- package/src/sdkgen.ts +4 -1
- package/src/testkit.ts +2 -2
- 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
|
@@ -308,13 +308,71 @@ let () =
|
|
|
308
308
|
ignore (u.u_fetcher ctx "http://h/a" (jo [("method", Str "GET"); ("headers", empty_map ())]));
|
|
309
309
|
check "order" (!order = ["second"; "first"; "server"]));
|
|
310
310
|
|
|
311
|
+
(* prepare_auth IS GENERATED (Sdk_prepare_auth), so these four cases ask
|
|
312
|
+
* the generated module where this SDK puts its credential instead of
|
|
313
|
+
* assuming a header. They used to read `spec.headers["authorization"]`
|
|
314
|
+
* outright, which made them pass only for a header scheme and FAIL on
|
|
315
|
+
* every apiKey-in-query SDK - joplin's `?token=` - i.e. on exactly the
|
|
316
|
+
* SDKs the model-driven placement exists to make work. The assertions
|
|
317
|
+
* still drive the real u_prepare_auth end to end and check the bag, the
|
|
318
|
+
* key and the value it actually wrote; only the expectation is read from
|
|
319
|
+
* the SDK's own declared contract rather than hardcoded. *)
|
|
320
|
+
let auth_bag (ctx : ctx) : value =
|
|
321
|
+
let sp = match ctx.c_spec with Some s -> s | None -> assert false in
|
|
322
|
+
if Sdk_prepare_auth.cred_where = "query" then sp.sp_query else sp.sp_headers in
|
|
323
|
+
|
|
324
|
+
(* A cookie rides the shared `cookie` header, so the KEY is that header and
|
|
325
|
+
* the VALUE is a `name=value` pair. A header and a query parameter are
|
|
326
|
+
* keyed by the credential name itself. *)
|
|
327
|
+
let auth_key =
|
|
328
|
+
if Sdk_prepare_auth.cred_where = "cookie" then "cookie"
|
|
329
|
+
else Sdk_prepare_auth.cred_name in
|
|
330
|
+
|
|
331
|
+
(* A header carries the auth.prefix, space-joined when it is non-empty. A
|
|
332
|
+
* query parameter and a cookie do NOT: `?token=Bearer%20abc` is not a
|
|
333
|
+
* thing any API reads. *)
|
|
334
|
+
let auth_placed (prefix : string) (cred : string) : string =
|
|
335
|
+
if Sdk_prepare_auth.cred_where = "query" then cred
|
|
336
|
+
else if Sdk_prepare_auth.cred_where = "cookie" then
|
|
337
|
+
Sdk_prepare_auth.cred_name ^ "=" ^ cred
|
|
338
|
+
else if prefix <> "" then prefix ^ " " ^ cred
|
|
339
|
+
else cred in
|
|
340
|
+
|
|
341
|
+
(* An SDK built with main.kit.config.auth.active: false places nothing at
|
|
342
|
+
* all, by design, so the two placement cases assert the absence instead. *)
|
|
343
|
+
let check_placed (label : string) (ctx : ctx) (prefix : string) (cred : string) =
|
|
344
|
+
if Sdk_prepare_auth.cred_active then
|
|
345
|
+
check_vstr label (getp (auth_bag ctx) auth_key) (auth_placed prefix cred)
|
|
346
|
+
else
|
|
347
|
+
check (label ^ ": auth off, nothing placed")
|
|
348
|
+
(is_noval (getp (auth_bag ctx) auth_key)) in
|
|
349
|
+
|
|
350
|
+
(* The stale credential a clearing case must remove, written the way
|
|
351
|
+
* prepare_auth would have written it - a bare value for a header or a
|
|
352
|
+
* query parameter, a `name=value` pair for a cookie, since cookie_set
|
|
353
|
+
* splices OUR pair out and leaves every other cookie alone. *)
|
|
354
|
+
let stale_spec () : spec =
|
|
355
|
+
let placed = Str (auth_placed "" "stale") in
|
|
356
|
+
if Sdk_prepare_auth.cred_where = "query" then
|
|
357
|
+
new_spec (jo [("headers", empty_map ()); ("query", jo [(auth_key, placed)])])
|
|
358
|
+
else
|
|
359
|
+
new_spec (jo [("headers", jo [(auth_key, placed)])]) in
|
|
360
|
+
|
|
361
|
+
(* With auth switched off nothing is removed either: the function is a
|
|
362
|
+
* no-op by design, so a stale value is expected to survive. *)
|
|
363
|
+
let check_cleared (label : string) (ctx : ctx) =
|
|
364
|
+
let got = getp (auth_bag ctx) auth_key in
|
|
365
|
+
if Sdk_prepare_auth.cred_active then check label (is_noval got)
|
|
366
|
+
else check (label ^ ": auth off, nothing touched")
|
|
367
|
+
(got = Str (auth_placed "" "stale")) in
|
|
368
|
+
|
|
311
369
|
test "prepare_auth.apikey_prefix_space_joined" (fun () ->
|
|
312
370
|
let cl = client () in
|
|
313
371
|
let ctx = mk_ctx cl "load" in
|
|
314
372
|
cl.cl_options <- jo [("apikey", Str "K"); ("auth", jo [("prefix", Str "Bearer")])];
|
|
315
373
|
ctx.c_spec <- Some (new_spec (jo [("headers", empty_map ())]));
|
|
316
374
|
ignore (cl.cl_utility.u_prepare_auth ctx);
|
|
317
|
-
|
|
375
|
+
check_placed "auth" ctx "Bearer" "K");
|
|
318
376
|
|
|
319
377
|
test "prepare_auth.raw_apikey" (fun () ->
|
|
320
378
|
let cl = client () in
|
|
@@ -322,23 +380,23 @@ let () =
|
|
|
322
380
|
cl.cl_options <- jo [("apikey", Str "K"); ("auth", jo [("prefix", Str "")])];
|
|
323
381
|
ctx.c_spec <- Some (new_spec (jo [("headers", empty_map ())]));
|
|
324
382
|
ignore (cl.cl_utility.u_prepare_auth ctx);
|
|
325
|
-
|
|
383
|
+
check_placed "auth" ctx "" "K");
|
|
326
384
|
|
|
327
385
|
test "prepare_auth.empty_apikey_drops_header" (fun () ->
|
|
328
386
|
let cl = client () in
|
|
329
387
|
let ctx = mk_ctx cl "load" in
|
|
330
388
|
cl.cl_options <- jo [("apikey", Str ""); ("auth", jo [("prefix", Str "Bearer")])];
|
|
331
|
-
ctx.c_spec <- Some (
|
|
389
|
+
ctx.c_spec <- Some (stale_spec ());
|
|
332
390
|
ignore (cl.cl_utility.u_prepare_auth ctx);
|
|
333
|
-
|
|
391
|
+
check_cleared "dropped" ctx);
|
|
334
392
|
|
|
335
393
|
test "prepare_auth.no_auth_block_drops_header" (fun () ->
|
|
336
394
|
let cl = client () in
|
|
337
395
|
let ctx = mk_ctx cl "load" in
|
|
338
396
|
cl.cl_options <- jo [("apikey", Str "K")];
|
|
339
|
-
ctx.c_spec <- Some (
|
|
397
|
+
ctx.c_spec <- Some (stale_spec ());
|
|
340
398
|
ignore (cl.cl_utility.u_prepare_auth ctx);
|
|
341
|
-
|
|
399
|
+
check_cleared "dropped" ctx);
|
|
342
400
|
|
|
343
401
|
test "result_headers.no_headers_empty_map" (fun () ->
|
|
344
402
|
let cl = client () in
|
|
@@ -128,7 +128,16 @@ $REGISTRY{make_options} = sub {
|
|
|
128
128
|
'suffix' => '',
|
|
129
129
|
# `basic` and `secret`: HTTP Basic Auth needs a second credential and a
|
|
130
130
|
# flag to say the pair is Basic rather than a single bearer token.
|
|
131
|
-
|
|
131
|
+
#
|
|
132
|
+
# `in` and `name`: WHERE the credential goes and UNDER WHAT NAME -
|
|
133
|
+
# header (the default, `authorization`), query or cookie. This shape
|
|
134
|
+
# is CLOSED, so a config carrying a key it does not list makes every
|
|
135
|
+
# request fail validation: without these two entries an
|
|
136
|
+
# apiKey-in-query SDK (joplin's `?token=`) could not build its
|
|
137
|
+
# options at all. They mirror main.kit.optspec.auth, which the
|
|
138
|
+
# model-driven targets read directly, and '' means "whatever the
|
|
139
|
+
# spec resolved", which is what prepare_auth already baked in.
|
|
140
|
+
'auth' => { 'prefix' => '', 'basic' => $JF, 'in' => '', 'name' => '' },
|
|
132
141
|
'headers' => { '`$CHILD`' => '`$STRING`' },
|
|
133
142
|
'allow' => {
|
|
134
143
|
'method' => 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
|
|
@@ -153,7 +153,14 @@ class ProjectNameMakeOptions
|
|
|
153
153
|
'base' => 'http://localhost:8000',
|
|
154
154
|
'prefix' => '',
|
|
155
155
|
'suffix' => '',
|
|
156
|
-
|
|
156
|
+
// `in` and `name` say WHERE the credential goes and under what
|
|
157
|
+
// name (header | query | cookie). The generated config carries
|
|
158
|
+
// them whenever the spec's scheme is not the header/Authorization
|
|
159
|
+
// default, and validate REJECTS a key this spec does not declare,
|
|
160
|
+
// so an apiKey-in-query SDK cannot be constructed without them.
|
|
161
|
+
// '' means "take what the spec said", which is what prepare_auth
|
|
162
|
+
// was generated from.
|
|
163
|
+
'auth' => ['prefix' => '', 'basic' => false, 'in' => '', 'name' => ''],
|
|
157
164
|
'headers' => ['`$CHILD`' => '`$STRING`'],
|
|
158
165
|
'allow' => [
|
|
159
166
|
'method' => 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
|
|
@@ -78,7 +78,19 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
|
|
|
78
78
|
("base", Value::str("http://localhost:8000")),
|
|
79
79
|
("prefix", Value::str("")),
|
|
80
80
|
("suffix", Value::str("")),
|
|
81
|
-
|
|
81
|
+
// CLOSED - validate rejects any key it does not declare - so without
|
|
82
|
+
// `in` and `name` an apiKey-in-query SDK fails to construct on its
|
|
83
|
+
// own config with "Unexpected keys at field auth: in, name".
|
|
84
|
+
// '' means "take what the spec said".
|
|
85
|
+
(
|
|
86
|
+
"auth",
|
|
87
|
+
jo(vec![
|
|
88
|
+
("prefix", Value::str("")),
|
|
89
|
+
("basic", Value::Bool(false)),
|
|
90
|
+
("in", Value::str("")),
|
|
91
|
+
("name", Value::str("")),
|
|
92
|
+
]),
|
|
93
|
+
),
|
|
82
94
|
("headers", jo(vec![("`$CHILD`", Value::str("`$STRING`"))])),
|
|
83
95
|
(
|
|
84
96
|
"allow",
|
|
@@ -306,10 +306,23 @@ object MakeOptions {
|
|
|
306
306
|
val optspec = Json.parse(
|
|
307
307
|
"{"
|
|
308
308
|
+ "\"apikey\": \"\","
|
|
309
|
+
// The SECOND credential, for the HTTP Basic scheme
|
|
310
|
+
// (base64(apikey:secret)). Named by main.kit.optspec but missing
|
|
311
|
+
// here, and validate drops what it does not name - so a caller
|
|
312
|
+
// that supplied `secret` never had it reach prepareAuth.
|
|
313
|
+
+ "\"secret\": \"\","
|
|
309
314
|
+ "\"base\": \"http://localhost:8000\","
|
|
310
315
|
+ "\"prefix\": \"\","
|
|
311
316
|
+ "\"suffix\": \"\","
|
|
312
|
-
|
|
317
|
+
// WHERE the credential goes and under what name, plus the HTTP
|
|
318
|
+
// Basic switch. `Struct.validate` DROPS a key this spec does not
|
|
319
|
+
// name, so without these three the generated Config's auth block
|
|
320
|
+
// was trimmed back to `prefix` on the way in and the SDK could not
|
|
321
|
+
// see its own scheme - which made Config_scala's `in`/`name`
|
|
322
|
+
// emission inert. Mirrors main.kit.optspec.auth in
|
|
323
|
+
// @voxgig/sdkgen/model/sdkgen.aon, which ts builds its OPTSPEC from.
|
|
324
|
+
+ "\"auth\": { \"prefix\": \"\", \"basic\": false,"
|
|
325
|
+
+ " \"in\": \"\", \"name\": \"\" },"
|
|
313
326
|
+ "\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
|
|
314
327
|
+ "\"allow\": {"
|
|
315
328
|
+ " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
|
|
@@ -108,43 +108,19 @@ object PrepareBody {
|
|
|
108
108
|
if ("data" == ctx.op.input) ctx.utility.transformRequest(ctx) else null
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
object PrepareAuth
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
130
|
-
|
|
131
|
-
var skip = false
|
|
132
|
-
if (apikey == null) skip = true
|
|
133
|
-
else apikey match {
|
|
134
|
-
case s: String if NOT_FOUND == s || "" == s => skip = true
|
|
135
|
-
case _ =>
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (skip) {
|
|
139
|
-
headers.remove(HEADER_AUTH)
|
|
140
|
-
} else {
|
|
141
|
-
var authPrefix = ""
|
|
142
|
-
Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => authPrefix = s; case _ => }
|
|
143
|
-
val apikeyVal = apikey match { case s: String => s; case _ => "" }
|
|
144
|
-
if ("" == authPrefix) headers.put(HEADER_AUTH, apikeyVal)
|
|
145
|
-
else headers.put(HEADER_AUTH, authPrefix + " " + apikeyVal)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
spec
|
|
149
|
-
}
|
|
150
|
-
}
|
|
111
|
+
// NO `object PrepareAuth` HERE, and its absence is the point.
|
|
112
|
+
//
|
|
113
|
+
// WHERE the credential goes is a fact about the API - header, query or
|
|
114
|
+
// cookie, under the name the spec gives - and apidef resolves all of it
|
|
115
|
+
// into main.kit.info.security. This file can hold only ONE answer, so the
|
|
116
|
+
// object that used to sit here hardcoded `val HEADER_AUTH = "authorization"`
|
|
117
|
+
// and an apiKey-in-query API (joplin's `?token=`) got a header it ignores.
|
|
118
|
+
//
|
|
119
|
+
// So prepareAuth is GENERATED, into utility/PrepareAuth.scala beside this
|
|
120
|
+
// file, by src/cmp/scala/PrepareAuth_scala.ts - which emits the one branch
|
|
121
|
+
// this API actually uses. It stays `object PrepareAuth` in this same
|
|
122
|
+
// package, so utility/Register.scala's `u.prepareAuth = (ctx) =>
|
|
123
|
+
// PrepareAuth.prepareAuth(ctx)` binds it with no change: scala resolves the
|
|
124
|
+
// object by package, not by file name.
|
|
125
|
+
//
|
|
126
|
+
// The six objects above are placement-independent and stay templated.
|
|
@@ -4,8 +4,30 @@
|
|
|
4
4
|
import Foundation
|
|
5
5
|
|
|
6
6
|
private func buildOptSpec() -> Value {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// THE AUTH OPTION BLOCK, and every key the generated config can put in it.
|
|
8
|
+
//
|
|
9
|
+
// This spec is what `validate` checks the merged options against, and a key
|
|
10
|
+
// it does not name does not survive - so the generated Config's auth block
|
|
11
|
+
// was trimmed back to `prefix` on the way in and the SDK could not see its
|
|
12
|
+
// own scheme.
|
|
13
|
+
//
|
|
14
|
+
// prefix the credential prefix (Bearer/Basic/OAuth, or '' for a raw key)
|
|
15
|
+
// basic the scheme is real HTTP Basic: base64(apikey:secret), not a
|
|
16
|
+
// single bearer token
|
|
17
|
+
// in WHERE the credential goes: header (the default) | query | cookie
|
|
18
|
+
// name the header, query parameter or cookie name it goes in under
|
|
19
|
+
//
|
|
20
|
+
// `in` and `name` mirror main.kit.optspec.auth in
|
|
21
|
+
// @voxgig/sdkgen/model/sdkgen.aon, which the model-driven targets read
|
|
22
|
+
// directly; '' means "take what the spec said", which is what the generated
|
|
23
|
+
// prepareAuth was built from, so an SDK that sets neither behaves exactly as
|
|
24
|
+
// it did before. Without them an apiKey-in-query SDK (joplin's `?token=`)
|
|
25
|
+
// cannot even construct its own options.
|
|
26
|
+
let authSpec = VMap()
|
|
27
|
+
authSpec.entries["prefix"] = .string("")
|
|
28
|
+
authSpec.entries["basic"] = .bool(false)
|
|
29
|
+
authSpec.entries["in"] = .string("")
|
|
30
|
+
authSpec.entries["name"] = .string("")
|
|
9
31
|
|
|
10
32
|
let headers = VMap()
|
|
11
33
|
headers.entries["`$CHILD`"] = .string("`$STRING`")
|
|
@@ -38,10 +60,15 @@ private func buildOptSpec() -> Value {
|
|
|
38
60
|
|
|
39
61
|
let spec = VMap()
|
|
40
62
|
spec.entries["apikey"] = .string("")
|
|
63
|
+
// The SECOND credential, for the HTTP Basic scheme
|
|
64
|
+
// (base64(apikey:secret)). Named by main.kit.optspec but missing here, and
|
|
65
|
+
// validate does not pass what it does not name - so a caller that supplied
|
|
66
|
+
// `secret` never had it reach prepareAuth.
|
|
67
|
+
spec.entries["secret"] = .string("")
|
|
41
68
|
spec.entries["base"] = .string("http://localhost:8000")
|
|
42
69
|
spec.entries["prefix"] = .string("")
|
|
43
70
|
spec.entries["suffix"] = .string("")
|
|
44
|
-
spec.entries["auth"] = .map(
|
|
71
|
+
spec.entries["auth"] = .map(authSpec)
|
|
45
72
|
spec.entries["headers"] = .map(headers)
|
|
46
73
|
spec.entries["allow"] = .map(allow)
|
|
47
74
|
spec.entries["entity"] = .map(entity)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ProjectName SDK utility: request preparation steps (method, path, params,
|
|
2
|
-
// query, headers, body
|
|
2
|
+
// query, headers, body) plus param resolution. The auth step is GENERATED
|
|
3
|
+
// into utility/PrepareAuth.swift - see the note below.
|
|
3
4
|
|
|
4
5
|
import Foundation
|
|
5
6
|
|
|
@@ -89,44 +90,25 @@ func prepareBodyUtil(_ ctx: Context) -> Value {
|
|
|
89
90
|
return .noval
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
private let
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
|
|
112
|
-
|
|
113
|
-
var skip = isNil(apikey)
|
|
114
|
-
if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
|
|
115
|
-
skip = true
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if skip {
|
|
119
|
-
headers.entries.removeValue(forKey: headerAuth)
|
|
120
|
-
} else {
|
|
121
|
-
var authPrefix = ""
|
|
122
|
-
if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
|
|
123
|
-
let apikeyVal = apikey.asString ?? ""
|
|
124
|
-
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
125
|
-
headers.entries[headerAuth] = .string(authPrefix == "" ? apikeyVal : authPrefix + " " + apikeyVal)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return spec
|
|
129
|
-
}
|
|
93
|
+
// prepareAuth IS NOT HERE. It was, and it hardcoded
|
|
94
|
+
//
|
|
95
|
+
// private let headerAuth = "authorization"
|
|
96
|
+
//
|
|
97
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API - header, query or
|
|
98
|
+
// cookie, and under what name - and apidef resolves it into
|
|
99
|
+
// main.kit.info.security. A template can only hold one answer, so an
|
|
100
|
+
// apiKey-in-query API (joplin's `?token=`) got an Authorization header it
|
|
101
|
+
// does not read and never got the query parameter it does.
|
|
102
|
+
//
|
|
103
|
+
// So `prepareAuthUtil` is GENERATED, into utility/PrepareAuth.swift beside
|
|
104
|
+
// this file, by cmp/swift/PrepareAuth_swift.ts. Same module, same internal
|
|
105
|
+
// symbol, so utility/Register.swift still binds it with
|
|
106
|
+
// `u.prepareAuth = prepareAuthUtil` and nothing else moved. Declaring it
|
|
107
|
+
// here as well would be an "invalid redeclaration" that fails the whole
|
|
108
|
+
// SwiftPM target.
|
|
109
|
+
//
|
|
110
|
+
// The seven functions above and paramUtil below do not depend on the model,
|
|
111
|
+
// so they stay templated.
|
|
130
112
|
|
|
131
113
|
func paramUtil(_ ctx: Context, _ paramdef: Value) -> Value {
|
|
132
114
|
let point = ctx.point
|
|
@@ -372,7 +372,18 @@ pub fn make_options_util(ctx: *Context) Value {
|
|
|
372
372
|
.{ "base", h.vstr("http://localhost:8000") },
|
|
373
373
|
.{ "prefix", h.vstr("") },
|
|
374
374
|
.{ "suffix", h.vstr("") },
|
|
375
|
-
|
|
375
|
+
// CLOSED - validate REJECTS any key this spec does not declare - so
|
|
376
|
+
// without `in` and `name` an apiKey-in-query SDK fails validation on
|
|
377
|
+
// its OWN generated config (Config_zig emits them whenever the
|
|
378
|
+
// scheme is not the header/Authorization default), and the whole
|
|
379
|
+
// merged options object is discarded. '' means "take what the spec
|
|
380
|
+
// said", which is what the generated prepare_auth was built from.
|
|
381
|
+
.{ "auth", h.jo(&.{
|
|
382
|
+
.{ "prefix", h.vstr("") },
|
|
383
|
+
.{ "basic", h.vbool(false) },
|
|
384
|
+
.{ "in", h.vstr("") },
|
|
385
|
+
.{ "name", h.vstr("") },
|
|
386
|
+
}) },
|
|
376
387
|
.{ "headers", h.jo(&.{.{ "`$CHILD`", h.vstr("`$STRING`") }}) },
|
|
377
388
|
.{ "allow", h.jo(&.{
|
|
378
389
|
.{ "method", h.vstr("GET,PUT,POST,PATCH,DELETE,OPTIONS") },
|
|
@@ -1308,50 +1319,31 @@ pub fn graphql_errors_util(ctx: *Context) bool {
|
|
|
1308
1319
|
return true;
|
|
1309
1320
|
}
|
|
1310
1321
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
h.del_prop(headers, h.vstr(HEADER_AUTH));
|
|
1337
|
-
} else {
|
|
1338
|
-
const auth_prefix: []const u8 = switch (h.getpath(&.{ "auth", "prefix" }, options)) {
|
|
1339
|
-
.string => |s| s,
|
|
1340
|
-
else => "",
|
|
1341
|
-
};
|
|
1342
|
-
const apikey_val: []const u8 = switch (apikey) {
|
|
1343
|
-
.string => |s| s,
|
|
1344
|
-
else => "",
|
|
1345
|
-
};
|
|
1346
|
-
if (auth_prefix.len == 0) {
|
|
1347
|
-
h.setp(headers, HEADER_AUTH, h.vstr(apikey_val));
|
|
1348
|
-
} else {
|
|
1349
|
-
h.setp(headers, HEADER_AUTH, h.vstr(fmt("{s} {s}", .{ auth_prefix, apikey_val })));
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
return spec;
|
|
1354
|
-
}
|
|
1322
|
+
// prepare_auth IS GENERATED (src/cmp/zig/PrepareAuth_zig.ts), not templated.
|
|
1323
|
+
//
|
|
1324
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API. apidef resolves the
|
|
1325
|
+
// security scheme's `in` and `name` into main.kit.info.security - joplin's
|
|
1326
|
+
// says `in: "query", name: "token"` - and this file could hold only one
|
|
1327
|
+
// answer, which was `const HEADER_AUTH = "authorization"`. So an
|
|
1328
|
+
// apiKey-in-query API was sent a header it does not read and never sent the
|
|
1329
|
+
// query parameter it does. Header, query and cookie need three different
|
|
1330
|
+
// bodies; a component emits the one this API uses and nothing else.
|
|
1331
|
+
//
|
|
1332
|
+
// RE-EXPORTED, NOT REWIRED. Every caller keeps naming the same symbol:
|
|
1333
|
+
// `Utility.prepare_auth` above, `make_spec_util`'s unqualified
|
|
1334
|
+
// `try prepare_auth_util(ctx)`, and `sdk.utilmod.prepare_auth_util` - the
|
|
1335
|
+
// path root.zig publishes and test/primary_utility_test.zig drives the
|
|
1336
|
+
// shared corpus's `prepareAuth` section through. A file-scope const bound to
|
|
1337
|
+
// the generated function is the whole of the binding change.
|
|
1338
|
+
pub const prepare_auth_util = @import("prepare_auth.zig").prepare_auth_util;
|
|
1339
|
+
|
|
1340
|
+
// The two comptime FACTS the generated file decided: where this SDK puts its
|
|
1341
|
+
// credential ("header" | "query" | "cookie" | "none"), and whether the scheme
|
|
1342
|
+
// is genuine HTTP Basic. Re-exported on the same path as the function, so
|
|
1343
|
+
// test/pipeline_test.zig can reach both as `sdk.utilmod.<name>` and skip the
|
|
1344
|
+
// header-shape cases on an SDK that has no header credential to assert on.
|
|
1345
|
+
pub const prepare_auth_placement = @import("prepare_auth.zig").PLACEMENT;
|
|
1346
|
+
pub const prepare_auth_basic = @import("prepare_auth.zig").BASIC;
|
|
1355
1347
|
|
|
1356
1348
|
pub fn result_basic_util(ctx: *Context) ?*SdkResult {
|
|
1357
1349
|
const response = ctx.response;
|
|
@@ -367,6 +367,35 @@ test "pipeline feature_add: ordering before after replace" {
|
|
|
367
367
|
// prepare_auth
|
|
368
368
|
// =====================================================================
|
|
369
369
|
|
|
370
|
+
// WHERE THE CREDENTIAL GOES IS MODEL-DRIVEN: core/prepare_auth.zig is
|
|
371
|
+
// GENERATED from the API's security scheme, and has exactly one branch. The
|
|
372
|
+
// cases below assert the HEADER shape - a key in `spec.headers` named
|
|
373
|
+
// `authorization` - which is the right assertion for the default and the
|
|
374
|
+
// wrong one for the other three. An SDK whose scheme puts the credential in
|
|
375
|
+
// the query string (`?token=`) or a cookie, and one whose project set
|
|
376
|
+
// `main.kit.config.auth.active: false`, correctly place NOTHING in the header
|
|
377
|
+
// bag, so these would go red for a reason that is not a defect.
|
|
378
|
+
//
|
|
379
|
+
// The generated file exports what it chose as a COMPTIME constant, and
|
|
380
|
+
// core/utility.zig re-exports it. Not read from the options: a query SDK's
|
|
381
|
+
// `options.auth.in` does say "query", but an auth-OFF SDK looks exactly like
|
|
382
|
+
// a header one there - Config omits `options.auth` entirely when auth is
|
|
383
|
+
// inactive and the optspec then supplies the same empty defaults.
|
|
384
|
+
//
|
|
385
|
+
// A header SDK - every SDK whose scheme resolves to the default - runs all of
|
|
386
|
+
// these exactly as it did before prepare_auth became a component.
|
|
387
|
+
fn headerCred() bool {
|
|
388
|
+
return std.mem.eql(u8, sdk.utilmod.prepare_auth_placement, "header");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// The two cases that assert how a single token is SHAPED into the header
|
|
392
|
+
// value need more than a header placement: genuine HTTP Basic replaces that
|
|
393
|
+
// shaping with base64(apikey:secret), and refuses outright when the secret is
|
|
394
|
+
// missing - which is exactly what those two fixtures leave out.
|
|
395
|
+
fn singleTokenCred() bool {
|
|
396
|
+
return headerCred() and !sdk.utilmod.prepare_auth_basic;
|
|
397
|
+
}
|
|
398
|
+
|
|
370
399
|
test "pipeline prepare_auth: guards missing spec" {
|
|
371
400
|
const client = sdk.test_sdk(vnull(), h.jo(&.{.{ "apikey", h.vstr("K") }}));
|
|
372
401
|
const utility = client.get_utility();
|
|
@@ -381,6 +410,7 @@ test "pipeline prepare_auth: apikey with prefix space joined" {
|
|
|
381
410
|
.{ "apikey", h.vstr("K") },
|
|
382
411
|
.{ "auth", h.jo(&.{.{ "prefix", h.vstr("Bearer") }}) },
|
|
383
412
|
}));
|
|
413
|
+
if (!singleTokenCred()) return;
|
|
384
414
|
const utility = client.get_utility();
|
|
385
415
|
const ctx = plCtx(client, utility, null);
|
|
386
416
|
ctx.spec = authSpec(vnull());
|
|
@@ -393,6 +423,7 @@ test "pipeline prepare_auth: raw apikey empty prefix as is" {
|
|
|
393
423
|
.{ "apikey", h.vstr("K") },
|
|
394
424
|
.{ "auth", h.jo(&.{.{ "prefix", h.vstr("") }}) },
|
|
395
425
|
}));
|
|
426
|
+
if (!singleTokenCred()) return;
|
|
396
427
|
const utility = client.get_utility();
|
|
397
428
|
const ctx = plCtx(client, utility, null);
|
|
398
429
|
ctx.spec = authSpec(vnull());
|
|
@@ -405,6 +436,7 @@ test "pipeline prepare_auth: empty apikey drops header" {
|
|
|
405
436
|
.{ "apikey", h.vstr("") },
|
|
406
437
|
.{ "auth", h.jo(&.{.{ "prefix", h.vstr("Bearer") }}) },
|
|
407
438
|
}));
|
|
439
|
+
if (!headerCred()) return;
|
|
408
440
|
const utility = client.get_utility();
|
|
409
441
|
const ctx = plCtx(client, utility, null);
|
|
410
442
|
ctx.spec = authSpec(h.jo(&.{.{ "authorization", h.vstr("stale") }}));
|
|
@@ -416,6 +448,7 @@ test "pipeline prepare_auth: missing apikey drops header" {
|
|
|
416
448
|
const client = sdk.test_sdk(vnull(), h.jo(&.{
|
|
417
449
|
.{ "auth", h.jo(&.{.{ "prefix", h.vstr("Bearer") }}) },
|
|
418
450
|
}));
|
|
451
|
+
if (!headerCred()) return;
|
|
419
452
|
const utility = client.get_utility();
|
|
420
453
|
const options = client.options_map();
|
|
421
454
|
// Skip if this SDK's options happen to carry a configured apikey.
|
|
@@ -430,6 +463,7 @@ test "pipeline prepare_auth: missing apikey drops header" {
|
|
|
430
463
|
|
|
431
464
|
test "pipeline prepare_auth: public api no auth block drops header" {
|
|
432
465
|
const client = sdk.test_sdk(vnull(), h.jo(&.{.{ "apikey", h.vstr("K") }}));
|
|
466
|
+
if (!headerCred()) return;
|
|
433
467
|
const utility = client.get_utility();
|
|
434
468
|
// Option validation supplies an auth shape for this SDK, so a truly
|
|
435
469
|
// auth-less client cannot be constructed here — mirror the rust skip.
|
|
@@ -274,7 +274,7 @@ syntax) with unification semantics:
|
|
|
274
274
|
| \`*default \\| type\` | A default value unified against a type (e.g. \`*true \\| boolean\`). |
|
|
275
275
|
| \`name: key()\` | Bind a field to its map key (so \`feature: log: {}\` gets \`name: 'log'\`). |
|
|
276
276
|
| \`$$path$$\` | Interpolate a model value into a string — e.g. the SDK \`name\`. |
|
|
277
|
-
| \`@"file.aon"\` | Include another fragment (how the index files work). |
|
|
277
|
+
| \`@"./file.aon"\` | Include another fragment (how the index files work). The \`./\` is required on a local path. |
|
|
278
278
|
| \`x: .y\` | Reference another path's value (e.g. \`deps: ts: .js\`). |
|
|
279
279
|
|
|
280
280
|
For example, the schema for every feature entry:
|
package/src/sdkgen.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
ActionResult,
|
|
18
18
|
} from './types'
|
|
19
19
|
|
|
20
|
-
import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix, isHttpBasicAuth,
|
|
20
|
+
import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix, resolveAuthIn, resolveAuthName, isAuthSuppressed, isHttpBasicAuth,
|
|
21
21
|
CONFIG_DATA_THRESHOLD, CONFIG_REPR_VALUES, isConfigData, configRepr,
|
|
22
22
|
configReprSetting, configDefinition, clean, rawStringLiteral } from './utility'
|
|
23
23
|
|
|
@@ -1220,6 +1220,9 @@ export {
|
|
|
1220
1220
|
requirePath,
|
|
1221
1221
|
isAuthActive,
|
|
1222
1222
|
resolveAuthPrefix,
|
|
1223
|
+
resolveAuthIn,
|
|
1224
|
+
resolveAuthName,
|
|
1225
|
+
isAuthSuppressed,
|
|
1223
1226
|
isHttpBasicAuth,
|
|
1224
1227
|
CONFIG_DATA_THRESHOLD,
|
|
1225
1228
|
CONFIG_REPR_VALUES,
|
package/src/testkit.ts
CHANGED
|
@@ -220,8 +220,8 @@ function stageConsumer(opts: StageOptions = {}): Consumer {
|
|
|
220
220
|
// kind arrives, rather than assuming every index is already wired.
|
|
221
221
|
Fs.writeFileSync(Path.join(sdk, 'model', 'sdk.aon'),
|
|
222
222
|
"name: '" + name + "'\n" +
|
|
223
|
-
'@"target/target-index.aon"\n' +
|
|
224
|
-
'@"feature/feature-index.aon"\n' +
|
|
223
|
+
'@"./target/target-index.aon"\n' +
|
|
224
|
+
'@"./feature/feature-index.aon"\n' +
|
|
225
225
|
(opts.extra ? opts.extra + '\n' : ''))
|
|
226
226
|
|
|
227
227
|
// `@voxgig/sdkgen` has to be resolvable FROM THE CONSUMER, because the
|