@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
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
Content,
|
|
4
|
+
File,
|
|
5
|
+
cmp,
|
|
6
|
+
isHttpBasicAuth,
|
|
7
|
+
resolveAuthIn,
|
|
8
|
+
resolveAuthName,
|
|
9
|
+
resolveAuthPrefix,
|
|
10
|
+
} from '@voxgig/sdkgen'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
KIT,
|
|
15
|
+
getModelPath,
|
|
16
|
+
} from '@voxgig/apidef'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
|
|
20
|
+
// rather than templated. The zig peer of PrepareAuth_ts; read that one
|
|
21
|
+
// first, it carries the full account of the defect.
|
|
22
|
+
//
|
|
23
|
+
// zig differs from the other twelve ports in ONE way: it had no
|
|
24
|
+
// prepare_auth template to delete. The logic was EMBEDDED in
|
|
25
|
+
// `tm/zig/core/utility.zig` — three file-scope constants and a
|
|
26
|
+
// `pub fn prepare_auth_util` in the middle of a 1500-line module that also
|
|
27
|
+
// holds make_spec, make_url, the result builders and the Utility bundle
|
|
28
|
+
// itself. So this is an EXTRACTION, not a replacement:
|
|
29
|
+
//
|
|
30
|
+
// core/prepare_auth.zig GENERATED here, one function.
|
|
31
|
+
// core/utility.zig keeps
|
|
32
|
+
// pub const prepare_auth_util =
|
|
33
|
+
// @import("prepare_auth.zig").prepare_auth_util;
|
|
34
|
+
//
|
|
35
|
+
// THE OWN-FILE SHAPE, not the whole-module-as-a-component fallback. A zig
|
|
36
|
+
// file IS a struct, so a function in its own file is a first-class
|
|
37
|
+
// compilation unit reached by a relative `@import` — no build.zig entry, no
|
|
38
|
+
// module declaration, nothing to register. Generating the whole of
|
|
39
|
+
// utility.zig instead would turn 1500 lines of hand-maintained template
|
|
40
|
+
// into a TypeScript string for the sake of 40, which is the opposite of the
|
|
41
|
+
// trade the other targets made.
|
|
42
|
+
//
|
|
43
|
+
// THE BINDING DOES NOT MOVE. Three call sites name this symbol and all three
|
|
44
|
+
// keep resolving to it through the re-export:
|
|
45
|
+
// - `Utility.prepare_auth` (core/utility.zig) — the method the pipeline
|
|
46
|
+
// dispatches through, called by core/sdk.zig (Main.fragment.zig line
|
|
47
|
+
// `self.sdkUtility.prepare_auth(ctx)`) and asserted on by
|
|
48
|
+
// test/pipeline_test.zig.
|
|
49
|
+
// - `make_spec_util` (core/utility.zig) — `try prepare_auth_util(ctx)`,
|
|
50
|
+
// unqualified, so the file-scope const is what it finds.
|
|
51
|
+
// - `sdk.utilmod.prepare_auth_util` — root.zig exports core/utility.zig as
|
|
52
|
+
// `utilmod`, and test/primary_utility_test.zig drives the shared
|
|
53
|
+
// corpus's `prepareAuth` section through it.
|
|
54
|
+
//
|
|
55
|
+
// A template cannot fix the defect, because the three placements need three
|
|
56
|
+
// different bodies and a template has to pick one. A component emits the
|
|
57
|
+
// branch this API actually uses and nothing else — and zig makes that
|
|
58
|
+
// stricter than most: an unused LOCAL is a compile error, so a file that
|
|
59
|
+
// bound `auth_prefix` and then dropped it for a query placement would not
|
|
60
|
+
// build at all.
|
|
61
|
+
const PrepareAuth = cmp(async function PrepareAuth(props: any) {
|
|
62
|
+
const { target } = props
|
|
63
|
+
const { model } = props.ctx$
|
|
64
|
+
|
|
65
|
+
const active = isAuthActive_zig(model)
|
|
66
|
+
const where = resolveAuthIn(model)
|
|
67
|
+
const name = resolveAuthName(model)
|
|
68
|
+
// Read so the resolution is visible at generation time even though the
|
|
69
|
+
// emitted code takes the prefix from `options.auth.prefix` at RUNTIME -
|
|
70
|
+
// it has to, because the secrets feature rewrites it there.
|
|
71
|
+
const prefix = resolveAuthPrefix(model)
|
|
72
|
+
const basic = isHttpBasicAuth(model)
|
|
73
|
+
|
|
74
|
+
// FOLDER NESTING — NO Folder IS OPENED HERE, and that is worked out from
|
|
75
|
+
// two facts, not assumed.
|
|
76
|
+
//
|
|
77
|
+
// 1. The template this extracts from is `tm/zig/core/utility.zig`, and
|
|
78
|
+
// Main_zig's `Copy({from: 'tm/zig'})` runs at the TARGET ROOT with no
|
|
79
|
+
// folder open (zig's layout is flat there: core/, feature/, utility/,
|
|
80
|
+
// entity/, test/ — Main_zig's own comment says the idiomatic src/ is
|
|
81
|
+
// deliberately not used). So that template lands at `<root>/core/`,
|
|
82
|
+
// and the extracted function must land in the same directory for
|
|
83
|
+
// `@import("prepare_auth.zig")` — a SIBLING-relative path — to resolve.
|
|
84
|
+
//
|
|
85
|
+
// 2. Main_zig ALREADY opens `Folder({name: 'core'})` for core/sdk.zig and
|
|
86
|
+
// for Config, and that is where this component is called. Config_zig
|
|
87
|
+
// writes `File({name: 'config.zig'})` with no Folder of its own for
|
|
88
|
+
// exactly this reason.
|
|
89
|
+
//
|
|
90
|
+
// Opening a second `core` here would write `core/core/prepare_auth.zig`:
|
|
91
|
+
// a file no `@import` names, invisible to `zig build` (which analyses only
|
|
92
|
+
// what a module root reaches), while core/utility.zig's re-export fails to
|
|
93
|
+
// resolve — or, worse in the ts port's version of this trap, silently
|
|
94
|
+
// keeps using a stale sibling. Verified by generating the target and
|
|
95
|
+
// running `zig build test` against the result, not by eye: a file zig
|
|
96
|
+
// never reaches produces no error at all.
|
|
97
|
+
File({ name: 'prepare_auth.' + target.ext }, () => {
|
|
98
|
+
Content(render({ active, where, name, prefix, basic }))
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
type AuthSpec = {
|
|
104
|
+
active: boolean
|
|
105
|
+
where: string
|
|
106
|
+
name: string
|
|
107
|
+
prefix: string
|
|
108
|
+
basic: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const HEAD = `// prepare_auth — GENERATED from the model (src/cmp/zig/PrepareAuth_zig.ts),
|
|
113
|
+
// not copied from tm/zig, because WHERE THE CREDENTIAL GOES IS A FACT ABOUT
|
|
114
|
+
// THIS API. apidef resolves the security scheme's \`in\` and \`name\` into
|
|
115
|
+
// main.kit.info.security; a template can hold only one answer, and the one it
|
|
116
|
+
// held was an \`authorization\` header, so an apiKey-in-query API was sent a
|
|
117
|
+
// header it does not read and never sent the query parameter it does.
|
|
118
|
+
//
|
|
119
|
+
// This function used to live in core/utility.zig. It is re-exported from
|
|
120
|
+
// there (\`pub const prepare_auth_util = @import("prepare_auth.zig")...\`), so
|
|
121
|
+
// Utility.prepare_auth, make_spec_util and sdk.utilmod.prepare_auth_util all
|
|
122
|
+
// still reach the same symbol.
|
|
123
|
+
`
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
function render(spec: AuthSpec): string {
|
|
127
|
+
// NO GENERATION-TIME GATE ON WHETHER TO PLACE A CREDENTIAL AT ALL, beyond
|
|
128
|
+
// the project's own explicit switch — see isAuthActive_zig below for why
|
|
129
|
+
// `isAuthActive` is the wrong predicate here.
|
|
130
|
+
if (!spec.active) return renderInactive()
|
|
131
|
+
|
|
132
|
+
if ('query' === spec.where) return renderQuery(spec)
|
|
133
|
+
if ('cookie' === spec.where) return renderCookie(spec)
|
|
134
|
+
|
|
135
|
+
return renderHeader(spec)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
// AUTH SWITCHED OFF BY THE PROJECT (`main.kit.config.auth.active: false`).
|
|
140
|
+
// The SDK gets a prepare_auth that is honest about it rather than one that
|
|
141
|
+
// deletes a header nobody set. Nothing but the Context and Spec types is
|
|
142
|
+
// imported: zig tolerates an unused file-scope const, but a reader should
|
|
143
|
+
// not have to decide whether `vs` here means something.
|
|
144
|
+
function renderInactive(): string {
|
|
145
|
+
return HEAD + `//
|
|
146
|
+
// THIS SDK SENDS NO CREDENTIAL: the project set
|
|
147
|
+
// \`main.kit.config.auth.active: false\`, which is the documented, explicit
|
|
148
|
+
// way to say so. The function stays in the pipeline because make_spec calls
|
|
149
|
+
// it unconditionally.
|
|
150
|
+
|
|
151
|
+
const h = @import("helpers.zig");
|
|
152
|
+
const ctxmod = @import("context.zig");
|
|
153
|
+
const spec_mod = @import("spec.zig");
|
|
154
|
+
|
|
155
|
+
const Context = ctxmod.Context;
|
|
156
|
+
const Spec = spec_mod.Spec;
|
|
157
|
+
const E = h.E;
|
|
158
|
+
` + facts('header', false, false) + `
|
|
159
|
+
pub fn prepare_auth_util(ctx: *Context) E!*Spec {
|
|
160
|
+
const spec = ctx.spec orelse return ctx.fail("auth_no_spec", "Expected context spec property to be defined.");
|
|
161
|
+
return spec;
|
|
162
|
+
}
|
|
163
|
+
`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// HEADER — the default, and BYTE-FOR-BYTE the body that was in
|
|
168
|
+
// core/utility.zig when the scheme resolves to header/Authorization. Only
|
|
169
|
+
// the constant's VALUE moves with the model, plus the HTTP Basic block,
|
|
170
|
+
// which is emitted only for a basic scheme.
|
|
171
|
+
function renderHeader(spec: AuthSpec): string {
|
|
172
|
+
const withBasic = spec.basic
|
|
173
|
+
|
|
174
|
+
return HEAD + imports({ vs: true, fmt: true }) +
|
|
175
|
+
facts(spec.where, withBasic, true) + `
|
|
176
|
+
const CRED_NAME = ${zigstr(credLiteral(spec.where, spec.name))};
|
|
177
|
+
const OPTION_APIKEY = "apikey";
|
|
178
|
+
` + (withBasic ? `const OPTION_SECRET = "secret";
|
|
179
|
+
` : '') + `const NOT_FOUND = "__NOTFOUND__";
|
|
180
|
+
` + (withBasic ? basicHelpers() : '') + `
|
|
181
|
+
pub fn prepare_auth_util(ctx: *Context) E!*Spec {
|
|
182
|
+
const spec = ctx.spec orelse return ctx.fail("auth_no_spec", "Expected context spec property to be defined.");
|
|
183
|
+
|
|
184
|
+
const headers = spec.headers;
|
|
185
|
+
const options: Value = if (ctx.client) |client| client.options_map() else ctx.options;
|
|
186
|
+
|
|
187
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
188
|
+
const auth = h.getp(options, "auth");
|
|
189
|
+
if (h.is_noval(auth)) {
|
|
190
|
+
h.del_prop(headers, h.vstr(CRED_NAME));
|
|
191
|
+
return spec;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const apikey = vs.getprop(h.A(), options, h.vstr(OPTION_APIKEY), h.vstr(NOT_FOUND)) catch h.vstr(NOT_FOUND);
|
|
195
|
+
|
|
196
|
+
const skip = switch (apikey) {
|
|
197
|
+
.null => true,
|
|
198
|
+
.string => |s| std.mem.eql(u8, s, NOT_FOUND) or s.len == 0,
|
|
199
|
+
else => false,
|
|
200
|
+
};
|
|
201
|
+
` + (withBasic ? basicBlock() : '') + `
|
|
202
|
+
if (skip) {
|
|
203
|
+
h.del_prop(headers, h.vstr(CRED_NAME));
|
|
204
|
+
} else {
|
|
205
|
+
const auth_prefix: []const u8 = switch (h.getpath(&.{ "auth", "prefix" }, options)) {
|
|
206
|
+
.string => |s| s,
|
|
207
|
+
else => "",
|
|
208
|
+
};
|
|
209
|
+
const apikey_val: []const u8 = switch (apikey) {
|
|
210
|
+
.string => |s| s,
|
|
211
|
+
else => "",
|
|
212
|
+
};
|
|
213
|
+
// A raw credential (empty prefix, e.g. an apiKey scheme) must go in
|
|
214
|
+
// as-is; only a non-empty prefix (Bearer/Basic/OAuth) is space-joined.
|
|
215
|
+
if (auth_prefix.len == 0) {
|
|
216
|
+
h.setp(headers, CRED_NAME, h.vstr(apikey_val));
|
|
217
|
+
} else {
|
|
218
|
+
h.setp(headers, CRED_NAME, h.vstr(fmt("{s} {s}", .{ auth_prefix, apikey_val })));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return spec;
|
|
223
|
+
}
|
|
224
|
+
`
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
// QUERY — the credential is a query parameter, so it goes in spec.query and
|
|
229
|
+
// the header bag is never touched. No `fmt`, because nothing is joined:
|
|
230
|
+
// zig would accept an unused file-scope helper, but emitting one invites the
|
|
231
|
+
// reader to look for the concatenation that is deliberately absent.
|
|
232
|
+
function renderQuery(spec: AuthSpec): string {
|
|
233
|
+
return HEAD + imports({ vs: true, fmt: false }) +
|
|
234
|
+
facts(spec.where, false, true) + `
|
|
235
|
+
const CRED_NAME = ${zigstr(credLiteral(spec.where, spec.name))};
|
|
236
|
+
const OPTION_APIKEY = "apikey";
|
|
237
|
+
const NOT_FOUND = "__NOTFOUND__";
|
|
238
|
+
|
|
239
|
+
pub fn prepare_auth_util(ctx: *Context) E!*Spec {
|
|
240
|
+
const spec = ctx.spec orelse return ctx.fail("auth_no_spec", "Expected context spec property to be defined.");
|
|
241
|
+
|
|
242
|
+
const query = spec.query;
|
|
243
|
+
const options: Value = if (ctx.client) |client| client.options_map() else ctx.options;
|
|
244
|
+
|
|
245
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
246
|
+
const auth = h.getp(options, "auth");
|
|
247
|
+
if (h.is_noval(auth)) {
|
|
248
|
+
h.del_prop(query, h.vstr(CRED_NAME));
|
|
249
|
+
return spec;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const apikey = vs.getprop(h.A(), options, h.vstr(OPTION_APIKEY), h.vstr(NOT_FOUND)) catch h.vstr(NOT_FOUND);
|
|
253
|
+
|
|
254
|
+
const skip = switch (apikey) {
|
|
255
|
+
.null => true,
|
|
256
|
+
.string => |s| std.mem.eql(u8, s, NOT_FOUND) or s.len == 0,
|
|
257
|
+
else => false,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (skip) {
|
|
261
|
+
h.del_prop(query, h.vstr(CRED_NAME));
|
|
262
|
+
} else {
|
|
263
|
+
const apikey_val: []const u8 = switch (apikey) {
|
|
264
|
+
.string => |s| s,
|
|
265
|
+
else => "",
|
|
266
|
+
};
|
|
267
|
+
// NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
|
|
268
|
+
// thing any API reads: the prefix is a header convention, so it is
|
|
269
|
+
// dropped here deliberately rather than silently concatenated. Nor is
|
|
270
|
+
// there an HTTP Basic branch - \`Authorization: Basic base64(u:p)\`
|
|
271
|
+
// cannot be a query parameter.
|
|
272
|
+
h.setp(query, CRED_NAME, h.vstr(apikey_val));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return spec;
|
|
276
|
+
}
|
|
277
|
+
`
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
// COOKIE — a cookie IS a header, so the credential rides the header bag; but
|
|
282
|
+
// the `cookie` header is SHARED with whatever cookies the caller set, so the
|
|
283
|
+
// pair is spliced in and out rather than assigned over. Splicing is also what
|
|
284
|
+
// makes this idempotent: a retried request cannot carry the credential twice.
|
|
285
|
+
function renderCookie(spec: AuthSpec): string {
|
|
286
|
+
return HEAD + imports({ vs: true, fmt: true }) +
|
|
287
|
+
facts(spec.where, false, true) + `
|
|
288
|
+
const COOKIE_HEADER = "cookie";
|
|
289
|
+
const CRED_NAME = ${zigstr(credLiteral(spec.where, spec.name))};
|
|
290
|
+
const OPTION_APIKEY = "apikey";
|
|
291
|
+
const NOT_FOUND = "__NOTFOUND__";
|
|
292
|
+
|
|
293
|
+
// The cookie header minus our own pair, every other cookie left alone.
|
|
294
|
+
fn cookies_without_cred(headers: Value) []const u8 {
|
|
295
|
+
const existing: []const u8 = switch (h.getp(headers, COOKIE_HEADER)) {
|
|
296
|
+
.string => |s| s,
|
|
297
|
+
else => "",
|
|
298
|
+
};
|
|
299
|
+
if (existing.len == 0) return "";
|
|
300
|
+
|
|
301
|
+
var kept: []const u8 = "";
|
|
302
|
+
var it = std.mem.splitScalar(u8, existing, ';');
|
|
303
|
+
while (it.next()) |part| {
|
|
304
|
+
const piece = std.mem.trim(u8, part, " \\t");
|
|
305
|
+
if (piece.len == 0) continue;
|
|
306
|
+
if (std.mem.eql(u8, piece, CRED_NAME)) continue;
|
|
307
|
+
if (std.mem.startsWith(u8, piece, CRED_NAME ++ "=")) continue;
|
|
308
|
+
kept = if (kept.len == 0) piece else fmt("{s}; {s}", .{ kept, piece });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return kept;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Set (a value) or remove (null) our pair, leaving every other cookie in place.
|
|
315
|
+
fn apply_cookie(headers: Value, value: ?[]const u8) void {
|
|
316
|
+
const rest = cookies_without_cred(headers);
|
|
317
|
+
|
|
318
|
+
if (value) |v| {
|
|
319
|
+
// NO PREFIX IN A COOKIE either - a cookie carries a bare \`name=value\`
|
|
320
|
+
// pair, not a header's scheme-prefixed credential.
|
|
321
|
+
const pair = fmt("{s}={s}", .{ CRED_NAME, v });
|
|
322
|
+
h.setp(headers, COOKIE_HEADER, h.vstr(if (rest.len == 0) pair else fmt("{s}; {s}", .{ rest, pair })));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (rest.len == 0) {
|
|
327
|
+
h.del_prop(headers, h.vstr(COOKIE_HEADER));
|
|
328
|
+
} else {
|
|
329
|
+
h.setp(headers, COOKIE_HEADER, h.vstr(rest));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
pub fn prepare_auth_util(ctx: *Context) E!*Spec {
|
|
334
|
+
const spec = ctx.spec orelse return ctx.fail("auth_no_spec", "Expected context spec property to be defined.");
|
|
335
|
+
|
|
336
|
+
const headers = spec.headers;
|
|
337
|
+
const options: Value = if (ctx.client) |client| client.options_map() else ctx.options;
|
|
338
|
+
|
|
339
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
340
|
+
const auth = h.getp(options, "auth");
|
|
341
|
+
if (h.is_noval(auth)) {
|
|
342
|
+
apply_cookie(headers, null);
|
|
343
|
+
return spec;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const apikey = vs.getprop(h.A(), options, h.vstr(OPTION_APIKEY), h.vstr(NOT_FOUND)) catch h.vstr(NOT_FOUND);
|
|
347
|
+
|
|
348
|
+
const skip = switch (apikey) {
|
|
349
|
+
.null => true,
|
|
350
|
+
.string => |s| std.mem.eql(u8, s, NOT_FOUND) or s.len == 0,
|
|
351
|
+
else => false,
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
if (skip) {
|
|
355
|
+
apply_cookie(headers, null);
|
|
356
|
+
} else {
|
|
357
|
+
const apikey_val: []const u8 = switch (apikey) {
|
|
358
|
+
.string => |s| s,
|
|
359
|
+
else => "",
|
|
360
|
+
};
|
|
361
|
+
apply_cookie(headers, apikey_val);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return spec;
|
|
365
|
+
}
|
|
366
|
+
`
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
// The imports and type aliases, in core/utility.zig's own order and
|
|
371
|
+
// spelling. `E` comes from helpers rather than error.zig on purpose:
|
|
372
|
+
// error.zig's type is `ProjectNameError`, and the placeholder rewrite is
|
|
373
|
+
// applied by Copy, which this GENERATED file never travels through.
|
|
374
|
+
function imports(need: { vs: boolean, fmt: boolean }): string {
|
|
375
|
+
return `
|
|
376
|
+
const std = @import("std");
|
|
377
|
+
` + (need.vs ? `const vs = @import("voxgig-struct");
|
|
378
|
+
` : '') + `const h = @import("helpers.zig");
|
|
379
|
+
const ctxmod = @import("context.zig");
|
|
380
|
+
const spec_mod = @import("spec.zig");
|
|
381
|
+
|
|
382
|
+
const Value = h.Value;
|
|
383
|
+
const Context = ctxmod.Context;
|
|
384
|
+
const Spec = spec_mod.Spec;
|
|
385
|
+
const E = h.E;
|
|
386
|
+
` + (need.fmt ? `
|
|
387
|
+
fn fmt(comptime f: []const u8, args: anytype) []const u8 {
|
|
388
|
+
return std.fmt.allocPrint(h.A(), f, args) catch "";
|
|
389
|
+
}
|
|
390
|
+
` : '')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
// Emitted only for a basic scheme in a HEADER placement, so an ordinary
|
|
395
|
+
// bearer SDK carries neither the base64 encoder nor the boolean coercion.
|
|
396
|
+
function basicHelpers(): string {
|
|
397
|
+
return `
|
|
398
|
+
// Boolean-or-absent: an option that is unset, null or a non-boolean is false
|
|
399
|
+
// (core/utility.zig's is_true, which this file can no longer see).
|
|
400
|
+
fn is_true(v: Value) bool {
|
|
401
|
+
return switch (v) {
|
|
402
|
+
.bool => |b| b,
|
|
403
|
+
else => false,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Standard base64, arena-allocated - the encoding peer of sekreto's unbase64.
|
|
408
|
+
fn base64_std(text: []const u8) []const u8 {
|
|
409
|
+
const enc = std.base64.standard.Encoder;
|
|
410
|
+
const buf = h.A().alloc(u8, enc.calcSize(text.len)) catch return "";
|
|
411
|
+
return enc.encode(buf, text);
|
|
412
|
+
}
|
|
413
|
+
`
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
// HTTP Basic is header-only BY DEFINITION: the scheme is
|
|
418
|
+
// `Authorization: Basic base64(user:pass)`. It cannot be expressed as a query
|
|
419
|
+
// parameter or a cookie, so this branch is emitted only where it can mean
|
|
420
|
+
// something — and only when the model says the scheme IS basic.
|
|
421
|
+
function basicBlock(): string {
|
|
422
|
+
return `
|
|
423
|
+
// True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
424
|
+
// token in the header (the branch below) can never authenticate against
|
|
425
|
+
// an API that actually checks \`Authorization: Basic base64(user:pass)\`.
|
|
426
|
+
if (is_true(h.getpath(&.{ "auth", "basic" }, options))) {
|
|
427
|
+
const secret = vs.getprop(h.A(), options, h.vstr(OPTION_SECRET), h.vstr(NOT_FOUND)) catch h.vstr(NOT_FOUND);
|
|
428
|
+
|
|
429
|
+
const no_secret = switch (secret) {
|
|
430
|
+
.null => true,
|
|
431
|
+
.string => |s| std.mem.eql(u8, s, NOT_FOUND) or s.len == 0,
|
|
432
|
+
else => false,
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
if (skip or no_secret) {
|
|
436
|
+
h.del_prop(headers, h.vstr(CRED_NAME));
|
|
437
|
+
} else {
|
|
438
|
+
const basic_prefix: []const u8 = switch (h.getpath(&.{ "auth", "prefix" }, options)) {
|
|
439
|
+
.string => |s| s,
|
|
440
|
+
else => "",
|
|
441
|
+
};
|
|
442
|
+
const apikey_val: []const u8 = switch (apikey) {
|
|
443
|
+
.string => |s| s,
|
|
444
|
+
else => "",
|
|
445
|
+
};
|
|
446
|
+
const secret_val: []const u8 = switch (secret) {
|
|
447
|
+
.string => |s| s,
|
|
448
|
+
else => "",
|
|
449
|
+
};
|
|
450
|
+
const b64 = base64_std(fmt("{s}:{s}", .{ apikey_val, secret_val }));
|
|
451
|
+
if (basic_prefix.len == 0) {
|
|
452
|
+
h.setp(headers, CRED_NAME, h.vstr(b64));
|
|
453
|
+
} else {
|
|
454
|
+
h.setp(headers, CRED_NAME, h.vstr(fmt("{s} {s}", .{ basic_prefix, b64 })));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return spec;
|
|
459
|
+
}
|
|
460
|
+
`
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
// The credential's key, as it goes into the bag.
|
|
465
|
+
//
|
|
466
|
+
// A HEADER name is LOWERCASED. HTTP header names are case-insensitive on the
|
|
467
|
+
// wire (RFC 9110 5.1), but this SDK's header bag is a plain Value map and
|
|
468
|
+
// object keys are case-SENSITIVE: the extracted template said
|
|
469
|
+
// `const HEADER_AUTH = "authorization";`, the shared corpus asserts
|
|
470
|
+
// `ctx:spec:headers:authorization`, tm/zig/test/pipeline_test.zig reads
|
|
471
|
+
// `h.getp(ctx.spec.?.headers, "authorization")`, and feature/secrets.zig
|
|
472
|
+
// rewrites the same lowercase key. apidef writes `name: "Authorization"`, so
|
|
473
|
+
// emitting it verbatim would put the credential where none of those look.
|
|
474
|
+
//
|
|
475
|
+
// A QUERY parameter and a COOKIE name are case-SENSITIVE to the API itself,
|
|
476
|
+
// so those go in exactly as the spec spells them: `?token=` is not `?Token=`.
|
|
477
|
+
function credLiteral(where: string, name: string): string {
|
|
478
|
+
return 'header' === where ? String(name).toLowerCase() : String(name)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
// NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING. That helper is also
|
|
483
|
+
// false whenever the SPEC declares no security scheme
|
|
484
|
+
// (`main.kit.info.auth: false`) — a statement about the DEFINITION, not a ban
|
|
485
|
+
// on ever sending a credential. Such an SDK still carries one: `optspec`
|
|
486
|
+
// always declares `apikey`, and make_options_util fills `options.auth` from
|
|
487
|
+
// the optspec defaults (tm/zig/core/utility.zig), so the runtime
|
|
488
|
+
// `h.is_noval(auth)` guard never fired and every such SDK has always sent
|
|
489
|
+
// `options.apikey`. Gating the body on `isAuthActive` does not trim dead
|
|
490
|
+
// code, it silently removes working authentication — and takes the secrets
|
|
491
|
+
// feature with it, since that resolves a secret into `options.apikey` and
|
|
492
|
+
// prepare_auth then places nothing. zig's own gated suite
|
|
493
|
+
// (`zig build test-secrets`) pins exactly that.
|
|
494
|
+
//
|
|
495
|
+
// `main.kit.config.auth.active: false` is the project saying "this SDK sends
|
|
496
|
+
// no credential, ever". It is the only signal that can honestly be honoured
|
|
497
|
+
// before runtime, so it is the only one used here.
|
|
498
|
+
function isAuthActive_zig(model: any): boolean {
|
|
499
|
+
const auth = getModelPath(model, `main.${KIT}.config.auth`,
|
|
500
|
+
{ only_active: false, required: false })
|
|
501
|
+
return !(null != auth && false === auth.active)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
// THE PLACEMENT AS A COMPTIME FACT, exported beside the function.
|
|
506
|
+
//
|
|
507
|
+
// The generated body has exactly ONE branch, chosen here at generation time,
|
|
508
|
+
// and nothing downstream can tell which by looking at the options: a query
|
|
509
|
+
// SDK's `options.auth.in` says "query", but an auth-OFF SDK is
|
|
510
|
+
// indistinguishable from a header one there (Config omits `options.auth`
|
|
511
|
+
// entirely when auth is inactive, and make_options' optspec then supplies the
|
|
512
|
+
// same empty defaults either way).
|
|
513
|
+
//
|
|
514
|
+
// tm/zig/test/pipeline_test.zig needs to tell them apart. Its prepare_auth
|
|
515
|
+
// cases assert the HEADER shape - `spec.headers["authorization"]` - which is
|
|
516
|
+
// the right assertion for the default and the wrong one for the other three,
|
|
517
|
+
// where the SDK correctly places nothing there. Rather than let a query SDK
|
|
518
|
+
// ship four red tests (or delete the coverage that every header SDK does
|
|
519
|
+
// want), the suite reads these two constants and skips what does not apply.
|
|
520
|
+
//
|
|
521
|
+
// core/utility.zig re-exports both, so the path is `sdk.utilmod.<name>`.
|
|
522
|
+
function facts(where: string, basic: boolean, active: boolean): string {
|
|
523
|
+
const placement = active ? where : 'none'
|
|
524
|
+
|
|
525
|
+
return `
|
|
526
|
+
/// WHERE this SDK places its credential: "header", "query", "cookie", or
|
|
527
|
+
/// "none" when the project set \`main.kit.config.auth.active: false\`.
|
|
528
|
+
pub const PLACEMENT = ${zigstr(placement)};
|
|
529
|
+
|
|
530
|
+
/// True when the scheme is genuine HTTP Basic - two credentials, base64-joined
|
|
531
|
+
/// - rather than a single token. Header-only by definition, so false for every
|
|
532
|
+
/// other placement.
|
|
533
|
+
pub const BASIC = ${(basic && 'header' === where && active) ? 'true' : 'false'};
|
|
534
|
+
`
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
function zigstr(s: string): string {
|
|
539
|
+
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
export {
|
|
544
|
+
PrepareAuth
|
|
545
|
+
}
|
|
@@ -604,25 +604,27 @@
|
|
|
604
604
|
(oset! result :ok false)
|
|
605
605
|
true))))
|
|
606
606
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
607
|
+
;; WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so HEADER-AUTH,
|
|
608
|
+
;; OPTION-APIKEY, NOT-FOUND and u-prepare-auth are GENERATED into
|
|
609
|
+
;; src/sdk/prepare_auth.clj rather than written here.
|
|
610
|
+
;;
|
|
611
|
+
;; apidef resolves the security scheme's `in` and `name` into
|
|
612
|
+
;; main.kit.info.security - joplin's says `in: "query", name: "token"` - and
|
|
613
|
+
;; this file used to hardcode an `authorization` HEADER, so an
|
|
614
|
+
;; apiKey-in-query API got a header it does not read and never got the query
|
|
615
|
+
;; parameter it does. Header, query and cookie need three different bodies
|
|
616
|
+
;; and a hand-written file has to pick one, so cmp/clojure/PrepareAuth_
|
|
617
|
+
;; clojure.ts emits the branch the API actually uses and nothing else.
|
|
618
|
+
;;
|
|
619
|
+
;; `load`, NOT `require`: the generated file opens with `(in-ns 'sdk.core)`
|
|
620
|
+
;; and is read INTO THIS NAMESPACE, right here, where the defn used to be.
|
|
621
|
+
;; A namespace of its own would need sdk.core's oget / ctx-error /
|
|
622
|
+
;; client-options-map while base-utility-map below needs its u-prepare-auth -
|
|
623
|
+
;; a load cycle, which Clojure refuses. This is the idiom clojure.core itself
|
|
624
|
+
;; uses to split across files, every helper above is already in scope, and
|
|
625
|
+
;; both `:prepare-auth u-prepare-auth` below and `core/HEADER-AUTH` (read by
|
|
626
|
+
;; feature/secrets) go on resolving exactly as before.
|
|
627
|
+
(load "prepare_auth")
|
|
626
628
|
|
|
627
629
|
(defn u-make-point [ctx]
|
|
628
630
|
(let [preset (out-get ctx "point")]
|
|
@@ -1032,7 +1034,16 @@
|
|
|
1032
1034
|
;; credential and a flag to say the pair is Basic rather
|
|
1033
1035
|
;; than a single bearer token. Absent from the shape, a
|
|
1034
1036
|
;; client passing them is refused with "Unexpected keys".
|
|
1035
|
-
|
|
1037
|
+
;; `in` and `name`: WHERE the credential goes and under what
|
|
1038
|
+
;; name. The generated config emits them whenever the spec's
|
|
1039
|
+
;; scheme is not the header/Authorization default, and
|
|
1040
|
+
;; `vs/validate` REFUSES a key this spec does not name
|
|
1041
|
+
;; ("Unexpected keys at field auth") - so without them here
|
|
1042
|
+
;; an apiKey-in-query SDK could not even be constructed.
|
|
1043
|
+
;; '' means "take what the spec said", which is what the
|
|
1044
|
+
;; generated prepare_auth was built from. Mirrors
|
|
1045
|
+
;; main.kit.optspec.auth in @voxgig/sdkgen/model/sdkgen.aon.
|
|
1046
|
+
"auth" (vs/jm "prefix" "" "basic" false "in" "" "name" "")
|
|
1036
1047
|
"headers" (vs/jm "`$CHILD`" "`$STRING`")
|
|
1037
1048
|
"allow" (vs/jm "method" "GET,PUT,POST,PATCH,DELETE,OPTIONS"
|
|
1038
1049
|
"op" "create,update,load,list,remove,command,direct,graphql")
|