@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,439 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Content,
|
|
3
|
+
File,
|
|
4
|
+
Folder,
|
|
5
|
+
cmp,
|
|
6
|
+
isAuthSuppressed,
|
|
7
|
+
isHttpBasicAuth,
|
|
8
|
+
resolveAuthIn,
|
|
9
|
+
resolveAuthName,
|
|
10
|
+
resolveAuthPrefix,
|
|
11
|
+
} from '@voxgig/sdkgen'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import { swiftString, swiftTargetDir } from './utility_swift'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
|
|
18
|
+
// rather than templated. The swift peer of cmp/ts/PrepareAuth_ts.ts; read
|
|
19
|
+
// that one first, it carries the full account of the defect.
|
|
20
|
+
//
|
|
21
|
+
// apidef has always resolved the scheme's `in` and `name` into
|
|
22
|
+
// `main.kit.info.security` — joplin's says `in: "query", name: "token"` —
|
|
23
|
+
// and generation dropped both, so the SDK sent a header the API does not
|
|
24
|
+
// read and never sent the query parameter it does. Four repos in the cedar
|
|
25
|
+
// fleet ship SDKs that cannot authenticate for this reason: joplin
|
|
26
|
+
// (`token`), pipedrive (`api_token`), trello (`key`), lm-umbrella
|
|
27
|
+
// (`apiKey`).
|
|
28
|
+
//
|
|
29
|
+
// A template cannot fix this, because the three placements need three
|
|
30
|
+
// different bodies and a template has to pick one. A component emits the
|
|
31
|
+
// branch this API actually uses and nothing else — no dead query code in a
|
|
32
|
+
// bearer-token SDK, and no runtime `if` on a value that is fixed at
|
|
33
|
+
// generation time.
|
|
34
|
+
//
|
|
35
|
+
// SWIFT WAS THE ODD ONE OUT: it had no prepare_auth template to delete.
|
|
36
|
+
// `prepareAuthUtil` was one function among eight in
|
|
37
|
+
// `tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift`, beside
|
|
38
|
+
// prepareMethod/Path/Params/Query/Headers/Body and paramUtil — none of
|
|
39
|
+
// which is model-dependent. So the EXTRACTION is the move, not a rewrite of
|
|
40
|
+
// the module: `prepareAuthUtil` and its three private constants move out of
|
|
41
|
+
// Prepare.swift into this generated `utility/PrepareAuth.swift`, and the
|
|
42
|
+
// other seven functions stay templated where they are.
|
|
43
|
+
//
|
|
44
|
+
// SWIFT LETS THAT WORK WITH NO REWIRING AT ALL. Every .swift file in the
|
|
45
|
+
// SwiftPM target is one MODULE, and a top-level `func` is `internal` by
|
|
46
|
+
// default — visible to the whole module, importable by nothing, declared
|
|
47
|
+
// once. So moving the function to another file in the same target changes
|
|
48
|
+
// no name and no import:
|
|
49
|
+
//
|
|
50
|
+
// core/Utility.swift `public var prepareAuth: ((Context) throws -> Spec)!`
|
|
51
|
+
// utility/Register.swift `u.prepareAuth = prepareAuthUtil` <- resolves here
|
|
52
|
+
// utility/Make.swift `let spec = try utility.prepareAuth(ctx)`
|
|
53
|
+
// cmp/swift/fragment/Main.fragment.swift `_ = try utility.prepareAuth(ctx)`
|
|
54
|
+
//
|
|
55
|
+
// all stand unchanged. The one thing swift does NOT tolerate is the
|
|
56
|
+
// function existing in BOTH files — same module, same name, "invalid
|
|
57
|
+
// redeclaration of 'prepareAuthUtil'", which fails the whole target — so
|
|
58
|
+
// the template copy is deleted in the same change. That is the check that
|
|
59
|
+
// makes a stale copy impossible here, and it is a compile error rather than
|
|
60
|
+
// the silent double-life the ts port hit (see the folder note below).
|
|
61
|
+
const PrepareAuth = cmp(async function PrepareAuth(props: any) {
|
|
62
|
+
const { target } = props
|
|
63
|
+
const { model } = props.ctx$
|
|
64
|
+
|
|
65
|
+
// AUTH SWITCHED OFF BY THE PROJECT — and ONLY by that.
|
|
66
|
+
//
|
|
67
|
+
// NOT `isAuthActive`, and the difference is load-bearing. That helper is
|
|
68
|
+
// false whenever `main.kit.info.auth` is false, which only says the SPEC
|
|
69
|
+
// declared no security scheme; such an SDK still carries a credential,
|
|
70
|
+
// because `optspec` always declares `apikey` and makeOptions fills
|
|
71
|
+
// `options.auth` from the optspec defaults — so the runtime
|
|
72
|
+
// `isNil(auth)` guard below never fires and these SDKs have ALWAYS sent
|
|
73
|
+
// `options.apikey`. Emitting a no-op for them silently breaks working
|
|
74
|
+
// authentication and takes the secrets feature with it (it resolves a
|
|
75
|
+
// secret into `options.apikey`, and prepareAuth then places nothing).
|
|
76
|
+
//
|
|
77
|
+
// `main.kit.config.auth.active: false` is the project saying "this SDK
|
|
78
|
+
// sends no credential, ever" — the only signal that can honestly be
|
|
79
|
+
// honoured before runtime. `isAuthSuppressed` in @voxgig/sdkgen IS that
|
|
80
|
+
// predicate (utility.ts), so it is used here rather than re-derived: the
|
|
81
|
+
// py port wrote its own `isAuthActive_py` before the shared helper
|
|
82
|
+
// existed, and two copies of one rule is how they drift.
|
|
83
|
+
const suppressed = isAuthSuppressed(model)
|
|
84
|
+
|
|
85
|
+
const where = resolveAuthIn(model)
|
|
86
|
+
|
|
87
|
+
// LOWERCASED FOR A HEADER, VERBATIM OTHERWISE. HTTP header names are
|
|
88
|
+
// case-insensitive on the wire (RFC 9110 5.1), but this SDK's header bag
|
|
89
|
+
// is a plain `VMap` keyed in lower case throughout: the code being
|
|
90
|
+
// extracted said `private let headerAuth = "authorization"`, the shared
|
|
91
|
+
// corpus asserts `ctx:spec:headers:authorization`, the shipped
|
|
92
|
+
// PipelineTest reads `headers.entries["authorization"]`, and
|
|
93
|
+
// SecretsFeature.swift writes `headers.entries["authorization"]` as the
|
|
94
|
+
// other credential writer. apidef writes `name: "Authorization"`, so
|
|
95
|
+
// emitting it verbatim would put the credential under a key nothing in
|
|
96
|
+
// this SDK reads. A query parameter and a cookie ARE case-sensitive
|
|
97
|
+
// (`?token=` is not `?Token=`), so those keep the spec's spelling.
|
|
98
|
+
const resolvedName = resolveAuthName(model)
|
|
99
|
+
const name = 'header' === where ? resolvedName.toLowerCase() : resolvedName
|
|
100
|
+
|
|
101
|
+
// Resolved, and deliberately NOT baked into the emitted source: the
|
|
102
|
+
// prefix is a RUNTIME option (`options.auth.prefix`) and the code being
|
|
103
|
+
// extracted read it from the options map, so a caller — and the secrets
|
|
104
|
+
// feature, which rewrites it there — can still override it per client.
|
|
105
|
+
// Only the PLACEMENT is fixed at generation time.
|
|
106
|
+
const prefix = resolveAuthPrefix(model)
|
|
107
|
+
|
|
108
|
+
const basic = isHttpBasicAuth(model)
|
|
109
|
+
|
|
110
|
+
// FOLDER NESTING, worked out from the template's own path and from
|
|
111
|
+
// Main_swift, not guessed.
|
|
112
|
+
//
|
|
113
|
+
// The file being replaced lives at
|
|
114
|
+
// `tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift`, and Main_swift
|
|
115
|
+
// copies that tree with `Folder({name:'Sources'})` +
|
|
116
|
+
// `Copy({from:'tm/swift/Sources/ProjectNameSDK', to: swiftTargetDir(model)})`
|
|
117
|
+
// — note the `to`, which is why the runtime lands under the API's own
|
|
118
|
+
// name (`Sources/<Name>Sdk/utility/`) rather than under the placeholder
|
|
119
|
+
// directory. So the full destination is
|
|
120
|
+
// `Sources/<Name>Sdk/utility/PrepareAuth.swift`.
|
|
121
|
+
//
|
|
122
|
+
// ALL THREE SEGMENTS ARE OPENED HERE, and Main calls this at its TOP
|
|
123
|
+
// level, beside `EntityTypes({ target })` — which is the swift precedent
|
|
124
|
+
// for exactly this (EntityTypes_swift opens
|
|
125
|
+
// `Sources`/`swiftTargetDir`/`entity` itself). Two other call sites were
|
|
126
|
+
// available and both are wrong:
|
|
127
|
+
//
|
|
128
|
+
// * inside Main's generated-sources block, beside `Config({target})`.
|
|
129
|
+
// That block sits inside `Folder({name:'core'})`, so this would write
|
|
130
|
+
// `Sources/<Name>Sdk/core/utility/PrepareAuth.swift`.
|
|
131
|
+
// * inside that block but one level out, opening only `utility`. That
|
|
132
|
+
// is correct TODAY and breaks the moment the surrounding folders
|
|
133
|
+
// move; self-contained is what EntityTypes_swift chose.
|
|
134
|
+
//
|
|
135
|
+
// Getting it wrong is not merely a misplaced file: SwiftPM compiles
|
|
136
|
+
// EVERY .swift under `Sources/<target>/`, so a stray copy at the wrong
|
|
137
|
+
// path is still in the module and still collides with the real one. Here
|
|
138
|
+
// that is a hard build failure — which is the good case, and the reason
|
|
139
|
+
// the ts port's `src/src/utility/` accident (a second `Folder('src')`,
|
|
140
|
+
// the stale file still imported, invisible to the suite) cannot repeat on
|
|
141
|
+
// swift.
|
|
142
|
+
Folder({ name: 'Sources' }, () => {
|
|
143
|
+
Folder({ name: swiftTargetDir(model) }, () => {
|
|
144
|
+
Folder({ name: 'utility' }, () => {
|
|
145
|
+
File({ name: 'PrepareAuth.' + target.ext }, () => {
|
|
146
|
+
Content(render({
|
|
147
|
+
Name: model.const.Name,
|
|
148
|
+
suppressed, where, name, prefix, basic,
|
|
149
|
+
}))
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
type AuthSpec = {
|
|
158
|
+
Name: string
|
|
159
|
+
suppressed: boolean
|
|
160
|
+
where: string
|
|
161
|
+
name: string
|
|
162
|
+
prefix: string
|
|
163
|
+
basic: boolean
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
function render(spec: AuthSpec): string {
|
|
168
|
+
// Foundation on every file is the tm/swift convention (Prepare.swift,
|
|
169
|
+
// Make.swift, MakeOptions.swift, Spec.swift all open with it), and the
|
|
170
|
+
// basic and cookie branches genuinely need it — `base64EncodedString()`
|
|
171
|
+
// and `trimmingCharacters(in:)` are Foundation. Swift does not warn on an
|
|
172
|
+
// unused import, so the header branch carrying it costs nothing and keeps
|
|
173
|
+
// the file shaped like its neighbours.
|
|
174
|
+
const head = `// ${spec.Name} SDK utility: prepareAuth - place the API credential.
|
|
175
|
+
//
|
|
176
|
+
// GENERATED, not templated: WHERE the credential goes - header, query or
|
|
177
|
+
// cookie, and under what name - is a fact about THIS API, and tm/ can only
|
|
178
|
+
// hold one answer. Extracted from utility/Prepare.swift, which keeps the
|
|
179
|
+
// seven prepare* functions that do not depend on the model. Bound by
|
|
180
|
+
// utility/Register.swift (\`u.prepareAuth = prepareAuthUtil\`) exactly as
|
|
181
|
+
// before: same module, same internal symbol, no import needed.
|
|
182
|
+
//
|
|
183
|
+
// See cmp/swift/PrepareAuth_swift.ts.
|
|
184
|
+
|
|
185
|
+
import Foundation
|
|
186
|
+
`
|
|
187
|
+
|
|
188
|
+
if (spec.suppressed) {
|
|
189
|
+
// The project set `main.kit.config.auth.active: false`. No credential,
|
|
190
|
+
// ever — so the SDK gets a prepareAuth that is honest about it rather
|
|
191
|
+
// than one that pops a header nobody set.
|
|
192
|
+
return head + `
|
|
193
|
+
// This SDK is configured with authentication OFF
|
|
194
|
+
// (main.kit.config.auth.active is false), so there is no credential to
|
|
195
|
+
// place. The function stays in the pipeline because Register binds it
|
|
196
|
+
// unconditionally and makeSpec calls it on every request.
|
|
197
|
+
func prepareAuthUtil(_ ctx: Context) throws -> Spec {
|
|
198
|
+
guard let spec = ctx.spec else {
|
|
199
|
+
throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return spec
|
|
203
|
+
}
|
|
204
|
+
`
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ('query' === spec.where) return head + renderQuery(spec)
|
|
208
|
+
if ('cookie' === spec.where) return head + renderCookie(spec)
|
|
209
|
+
|
|
210
|
+
return head + renderHeader(spec)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
// HEADER. Byte-for-byte the function that was in Prepare.swift when the
|
|
215
|
+
// scheme resolves to the defaults (header / Authorization) and is not
|
|
216
|
+
// Basic — same constant names, same `skip` shape, same removeValue /
|
|
217
|
+
// entries[...] idioms, same error. Only the constant's VALUE moves with the
|
|
218
|
+
// model, plus the Basic block, which is emitted only for a basic scheme.
|
|
219
|
+
function renderHeader(spec: AuthSpec): string {
|
|
220
|
+
const basicConst = spec.basic ? `
|
|
221
|
+
private let optionSecret = "secret"` : ''
|
|
222
|
+
|
|
223
|
+
return `
|
|
224
|
+
private let headerAuth = ${swiftString(spec.name)}
|
|
225
|
+
private let optionApikey = "apikey"${basicConst}
|
|
226
|
+
private let notFound = "__NOTFOUND__"
|
|
227
|
+
|
|
228
|
+
func prepareAuthUtil(_ ctx: Context) throws -> Spec {
|
|
229
|
+
guard let spec = ctx.spec else {
|
|
230
|
+
throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let headers = spec.headers
|
|
234
|
+
let options = ctx.client!.optionsMap()
|
|
235
|
+
|
|
236
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
237
|
+
let auth = getprop(.map(options), .string("auth"))
|
|
238
|
+
if isNil(auth) {
|
|
239
|
+
headers.entries.removeValue(forKey: headerAuth)
|
|
240
|
+
return spec
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
|
|
244
|
+
|
|
245
|
+
var skip = isNil(apikey)
|
|
246
|
+
if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
|
|
247
|
+
skip = true
|
|
248
|
+
}
|
|
249
|
+
${basicBlock(spec)}
|
|
250
|
+
if skip {
|
|
251
|
+
headers.entries.removeValue(forKey: headerAuth)
|
|
252
|
+
} else {
|
|
253
|
+
var authPrefix = ""
|
|
254
|
+
if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
|
|
255
|
+
let apikeyVal = apikey.asString ?? ""
|
|
256
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
257
|
+
headers.entries[headerAuth] = .string(authPrefix == "" ? apikeyVal : authPrefix + " " + apikeyVal)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return spec
|
|
261
|
+
}
|
|
262
|
+
`
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
// HTTP Basic is header-only BY DEFINITION: the scheme is
|
|
267
|
+
// `Authorization: Basic base64(user:pass)`. It cannot be expressed as a
|
|
268
|
+
// query parameter or a cookie, so the branch is emitted only where it can
|
|
269
|
+
// mean something — and only when the model says the scheme IS basic, so an
|
|
270
|
+
// ordinary bearer SDK carries no dead code and regenerates unchanged.
|
|
271
|
+
function basicBlock(spec: AuthSpec): string {
|
|
272
|
+
if (!spec.basic) return ''
|
|
273
|
+
|
|
274
|
+
return `
|
|
275
|
+
// True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
276
|
+
// token in the header (the branch below) can never authenticate against
|
|
277
|
+
// an API that actually checks \`Authorization: Basic base64(user:pass)\`.
|
|
278
|
+
if true == gpath(options, "auth", "basic").asBool {
|
|
279
|
+
let secret = getprop(.map(options), .string(optionSecret), .string(notFound))
|
|
280
|
+
|
|
281
|
+
var noSecret = isNil(secret)
|
|
282
|
+
if let secretStr = secret.asString, secretStr == notFound || secretStr == "" {
|
|
283
|
+
noSecret = true
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if skip || noSecret {
|
|
287
|
+
headers.entries.removeValue(forKey: headerAuth)
|
|
288
|
+
} else {
|
|
289
|
+
var authPrefix = ""
|
|
290
|
+
if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
|
|
291
|
+
let joined = (apikey.asString ?? "") + ":" + (secret.asString ?? "")
|
|
292
|
+
let b64 = Data(joined.utf8).base64EncodedString()
|
|
293
|
+
// Empty prefix (raw credential) must not add a leading space.
|
|
294
|
+
headers.entries[headerAuth] = .string(authPrefix == "" ? b64 : authPrefix + " " + b64)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return spec
|
|
298
|
+
}
|
|
299
|
+
`
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
// QUERY. The credential is a query parameter, so it goes in `spec.query` and
|
|
304
|
+
// the headers are never touched. `spec.query` is the bag makeUrl builds the
|
|
305
|
+
// query string from (utility/Make.swift), and prepareAuth runs AFTER
|
|
306
|
+
// prepareQuery in makeSpec, so the credential joins the op's own arguments
|
|
307
|
+
// rather than being overwritten by them.
|
|
308
|
+
function renderQuery(spec: AuthSpec): string {
|
|
309
|
+
return `
|
|
310
|
+
private let queryAuth = ${swiftString(spec.name)}
|
|
311
|
+
private let optionApikey = "apikey"
|
|
312
|
+
private let notFound = "__NOTFOUND__"
|
|
313
|
+
|
|
314
|
+
func prepareAuthUtil(_ ctx: Context) throws -> Spec {
|
|
315
|
+
guard let spec = ctx.spec else {
|
|
316
|
+
throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let query = spec.query
|
|
320
|
+
let options = ctx.client!.optionsMap()
|
|
321
|
+
|
|
322
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
323
|
+
let auth = getprop(.map(options), .string("auth"))
|
|
324
|
+
if isNil(auth) {
|
|
325
|
+
query.entries.removeValue(forKey: queryAuth)
|
|
326
|
+
return spec
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
|
|
330
|
+
|
|
331
|
+
var skip = isNil(apikey)
|
|
332
|
+
if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
|
|
333
|
+
skip = true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if skip {
|
|
337
|
+
query.entries.removeValue(forKey: queryAuth)
|
|
338
|
+
} else {
|
|
339
|
+
let apikeyVal = apikey.asString ?? ""
|
|
340
|
+
// NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
|
|
341
|
+
// thing any API reads: the prefix is a header convention, so it is
|
|
342
|
+
// dropped here deliberately rather than silently concatenated.
|
|
343
|
+
query.entries[queryAuth] = .string(apikeyVal)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return spec
|
|
347
|
+
}
|
|
348
|
+
`
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
// COOKIE. A cookie IS a header, so the credential rides the header bag -
|
|
353
|
+
// but the `cookie` header is SHARED with whatever cookies the caller set, so
|
|
354
|
+
// the pair is spliced in and out rather than the header assigned over.
|
|
355
|
+
// Splicing is also what makes it idempotent: a retried request cannot end up
|
|
356
|
+
// carrying the credential twice.
|
|
357
|
+
function renderCookie(spec: AuthSpec): string {
|
|
358
|
+
return `
|
|
359
|
+
private let cookieHeader = "cookie"
|
|
360
|
+
private let cookieAuth = ${swiftString(spec.name)}
|
|
361
|
+
private let optionApikey = "apikey"
|
|
362
|
+
private let notFound = "__NOTFOUND__"
|
|
363
|
+
|
|
364
|
+
// The cookie header minus our own pair, every other cookie untouched.
|
|
365
|
+
private func cookiesWithoutCred(_ headers: VMap) -> String {
|
|
366
|
+
guard let existing = headers.entries[cookieHeader]?.asString, existing != "" else {
|
|
367
|
+
return ""
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
var kept: [String] = []
|
|
371
|
+
for part in existing.split(separator: ";", omittingEmptySubsequences: false) {
|
|
372
|
+
let piece = part.trimmingCharacters(in: .whitespaces)
|
|
373
|
+
if piece == "" || piece == cookieAuth || piece.hasPrefix(cookieAuth + "=") {
|
|
374
|
+
continue
|
|
375
|
+
}
|
|
376
|
+
kept.append(piece)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return kept.joined(separator: "; ")
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Set (non-nil) or remove (nil) our pair, leaving every other cookie in
|
|
383
|
+
// place. Deleting the whole header to remove one pair would drop cookies
|
|
384
|
+
// this SDK never set.
|
|
385
|
+
private func applyCookie(_ headers: VMap, _ value: String?) {
|
|
386
|
+
let rest = cookiesWithoutCred(headers)
|
|
387
|
+
|
|
388
|
+
guard let value = value else {
|
|
389
|
+
if rest == "" {
|
|
390
|
+
headers.entries.removeValue(forKey: cookieHeader)
|
|
391
|
+
} else {
|
|
392
|
+
headers.entries[cookieHeader] = .string(rest)
|
|
393
|
+
}
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let pair = cookieAuth + "=" + value
|
|
398
|
+
headers.entries[cookieHeader] = .string(rest == "" ? pair : rest + "; " + pair)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
func prepareAuthUtil(_ ctx: Context) throws -> Spec {
|
|
402
|
+
guard let spec = ctx.spec else {
|
|
403
|
+
throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let headers = spec.headers
|
|
407
|
+
let options = ctx.client!.optionsMap()
|
|
408
|
+
|
|
409
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
410
|
+
let auth = getprop(.map(options), .string("auth"))
|
|
411
|
+
if isNil(auth) {
|
|
412
|
+
applyCookie(headers, nil)
|
|
413
|
+
return spec
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
|
|
417
|
+
|
|
418
|
+
var skip = isNil(apikey)
|
|
419
|
+
if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
|
|
420
|
+
skip = true
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if skip {
|
|
424
|
+
applyCookie(headers, nil)
|
|
425
|
+
} else {
|
|
426
|
+
// NO PREFIX IN A COOKIE either - a cookie carries a bare \`name=value\`
|
|
427
|
+
// pair, not a header's scheme-prefixed credential.
|
|
428
|
+
applyCookie(headers, apikey.asString ?? "")
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return spec
|
|
432
|
+
}
|
|
433
|
+
`
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
export {
|
|
438
|
+
PrepareAuth
|
|
439
|
+
}
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
isAuthActive,
|
|
16
16
|
isConfigData,
|
|
17
17
|
isHttpBasicAuth,
|
|
18
|
+
resolveAuthIn,
|
|
19
|
+
resolveAuthName,
|
|
18
20
|
resolveAuthPrefix,
|
|
19
21
|
serverVariables,
|
|
20
22
|
targetFeatures,
|
|
@@ -54,10 +56,19 @@ const Config = cmp(async function Config(props: any) {
|
|
|
54
56
|
// config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
|
|
55
57
|
const authPrefix = resolveAuthPrefix(model)
|
|
56
58
|
const authBasic = isHttpBasicAuth(model)
|
|
59
|
+
// `in` and `name` travel with the prefix now. They were resolved by
|
|
60
|
+
// apidef all along and dropped here, so an apiKey-in-query API got an
|
|
61
|
+
// Authorization header it does not read. Emitted only when they differ
|
|
62
|
+
// from the defaults, so a header/Authorization SDK is byte-identical to
|
|
63
|
+
// what it generated before.
|
|
64
|
+
const authIn = resolveAuthIn(model)
|
|
65
|
+
const authName = resolveAuthName(model)
|
|
57
66
|
const authBlock = authActive
|
|
58
67
|
? `auth: {
|
|
59
68
|
prefix: '${authPrefix}',${authBasic ? `
|
|
60
|
-
basic: true,` : ''}
|
|
69
|
+
basic: true,` : ''}${'header' === authIn ? '' : `
|
|
70
|
+
in: '${authIn}',`}${'Authorization' === authName ? '' : `
|
|
71
|
+
name: '${authName}',`}
|
|
61
72
|
},
|
|
62
73
|
|
|
63
74
|
`
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
|
|
25
25
|
import { Package } from './Package_ts'
|
|
26
26
|
import { Config } from './Config_ts'
|
|
27
|
+
import { PrepareAuth } from './PrepareAuth_ts'
|
|
27
28
|
import { Schema } from './Schema_ts'
|
|
28
29
|
import { Gitignore } from './Gitignore_ts'
|
|
29
30
|
import { MainEntity } from './MainEntity_ts'
|
|
@@ -193,6 +194,10 @@ if (fres instanceof Promise) { await fres }
|
|
|
193
194
|
})
|
|
194
195
|
|
|
195
196
|
Config({ target })
|
|
197
|
+
|
|
198
|
+
// GENERATED, NOT COPIED. Where the credential goes is a fact about the
|
|
199
|
+
// API, and tm/ can only hold one answer. See PrepareAuth_ts.
|
|
200
|
+
PrepareAuth({ target })
|
|
196
201
|
Schema({ target })
|
|
197
202
|
EntityBase({ target })
|
|
198
203
|
EntityTypes({ target })
|