@voxgig/sdkgen 4.19.1 → 4.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +6 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +4 -1
- package/dist/utility.js +60 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +10 -0
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +15 -0
- package/project/.sdk/src/cmp/c/Main_c.ts +15 -0
- package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +371 -0
- package/project/.sdk/src/cmp/clojure/Config_clojure.ts +47 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +11 -1
- package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +374 -0
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +40 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +17 -0
- package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +441 -0
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +42 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +18 -0
- package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +392 -0
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +36 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +15 -0
- package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +418 -0
- package/project/.sdk/src/cmp/go/Config_go.ts +12 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +14 -0
- package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +322 -0
- package/project/.sdk/src/cmp/java/Config_java.ts +20 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +14 -0
- package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +298 -0
- package/project/.sdk/src/cmp/js/Config_js.ts +12 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +6 -0
- package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +244 -0
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +23 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +20 -0
- package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +440 -0
- package/project/.sdk/src/cmp/lua/Config_lua.ts +15 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +11 -0
- package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +364 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +31 -1
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +19 -0
- package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +454 -0
- package/project/.sdk/src/cmp/perl/Config_perl.ts +30 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +14 -0
- package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +387 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +12 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +7 -0
- package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +325 -0
- package/project/.sdk/src/cmp/py/Config_py.ts +12 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +12 -0
- package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +389 -0
- package/project/.sdk/src/cmp/rb/Config_rb.ts +18 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +10 -0
- package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +305 -0
- package/project/.sdk/src/cmp/rust/Config_rust.ts +41 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +16 -0
- package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +410 -0
- package/project/.sdk/src/cmp/scala/Config_scala.ts +21 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +14 -0
- package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +415 -0
- package/project/.sdk/src/cmp/swift/Config_swift.ts +37 -1
- package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
- package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +439 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +12 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -0
- package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +237 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +33 -1
- package/project/.sdk/src/cmp/zig/Main_zig.ts +16 -0
- package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +545 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +31 -20
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +36 -40
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +11 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -32
- package/project/.sdk/tm/java/utility/MakeOptions.java +13 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +14 -1
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +13 -46
- package/project/.sdk/tm/ocaml/Makefile +11 -1
- package/project/.sdk/tm/ocaml/sdk_helpers.ml +15 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +31 -25
- package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +125 -12
- package/project/.sdk/tm/ocaml/test/t_pipeline.ml +64 -6
- package/project/.sdk/tm/perl/utility/make_options.pm +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +8 -1
- package/project/.sdk/tm/rust/utility/make_options.rs +13 -1
- package/project/.sdk/tm/scala/utility/Make.scala +14 -1
- package/project/.sdk/tm/scala/utility/Prepare.scala +16 -40
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +30 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +21 -39
- package/project/.sdk/tm/zig/core/utility.zig +37 -45
- package/project/.sdk/tm/zig/test/pipeline_test.zig +34 -0
- package/project/sdkgen-package.json +1 -1
- package/src/sdkgen.ts +4 -1
- package/src/utility.ts +75 -0
- package/project/.sdk/tm/c/utility/prepare_auth.c +0 -65
- package/project/.sdk/tm/csharp/utility/PrepareAuth.cs +0 -54
- package/project/.sdk/tm/go/utility/prepare_auth.go +0 -59
- package/project/.sdk/tm/java/utility/PrepareAuth.java +0 -66
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +0 -49
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +0 -52
- package/project/.sdk/tm/perl/utility/prepare_auth.pm +0 -56
- package/project/.sdk/tm/php/utility/PrepareAuth.php +0 -45
- package/project/.sdk/tm/py/pkg/utility/prepare_auth.py +0 -46
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +0 -35
- package/project/.sdk/tm/rust/utility/prepare_auth.rs +0 -65
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +0 -78
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
Content,
|
|
4
|
+
File,
|
|
5
|
+
Folder,
|
|
6
|
+
cmp,
|
|
7
|
+
isAuthSuppressed,
|
|
8
|
+
isHttpBasicAuth,
|
|
9
|
+
resolveAuthIn,
|
|
10
|
+
resolveAuthName,
|
|
11
|
+
resolveAuthPrefix,
|
|
12
|
+
} from '@voxgig/sdkgen'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import { kotlinPackage } from './utility_kotlin'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
|
|
19
|
+
// rather than templated.
|
|
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, hardcoding an `authorization` header. The
|
|
24
|
+
// result was an SDK that sent a header the API does not read and never
|
|
25
|
+
// sent the query parameter it does, so it could not authenticate at all.
|
|
26
|
+
// Four repos in the cedar fleet shipped that way: joplin (`token`),
|
|
27
|
+
// pipedrive (`api_token`), trello (`key`), lm-umbrella (`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. The ts port (PrepareAuth_ts) is the donor.
|
|
34
|
+
//
|
|
35
|
+
// KOTLIN IS AN EXTRACTION, NOT A REPLACEMENT. The other twelve targets had
|
|
36
|
+
// a `prepare_auth` template of their own to delete; kotlin did not — the
|
|
37
|
+
// function lived among eight unrelated `prepare*` helpers in
|
|
38
|
+
// `tm/kotlin/utility/Prepare.kt`. So `prepareAuth` (and only it, with the
|
|
39
|
+
// three constants nothing else in that file used) MOVES here, into its own
|
|
40
|
+
// compilation unit in the SAME package.
|
|
41
|
+
//
|
|
42
|
+
// That is a pure move as far as every caller is concerned, because a
|
|
43
|
+
// Kotlin top-level function is visible to the whole package without an
|
|
44
|
+
// import: `utility/Register.kt` keeps binding `u.prepareAuth = ::prepareAuth`
|
|
45
|
+
// unchanged, `core/Utility.kt` keeps its `lateinit var prepareAuth:
|
|
46
|
+
// (Context) -> Spec` and its `u.prepareAuth = this.prepareAuth` copy, and
|
|
47
|
+
// `core/SdkClient.kt` and `utility/MakeSpecUrl.kt` keep calling
|
|
48
|
+
// `utility.prepareAuth(ctx)` through that binding. Nothing is renamed and
|
|
49
|
+
// nothing is re-wired. The three constants were `private` (file-scoped), so
|
|
50
|
+
// carrying them here cannot collide with what stays behind.
|
|
51
|
+
const PrepareAuth = cmp(async function PrepareAuth(props: any) {
|
|
52
|
+
const { target } = props
|
|
53
|
+
const { model } = props.ctx$
|
|
54
|
+
|
|
55
|
+
const kotlinpackage = kotlinPackage(model)
|
|
56
|
+
|
|
57
|
+
const where = resolveAuthIn(model)
|
|
58
|
+
|
|
59
|
+
// LOWERCASED FOR A HEADER, VERBATIM OTHERWISE. `spec.headers` is a
|
|
60
|
+
// LinkedHashMap<String, Any?> — case-SENSITIVE — and every other writer
|
|
61
|
+
// and reader of an auth header in this target spells it lower case: the
|
|
62
|
+
// deleted template's own `HEADER_AUTH = "authorization"`, SecretsFeature's
|
|
63
|
+
// `headers["authorization"]` rewrite and removal, DebugFeature's redact
|
|
64
|
+
// list, PipelineTest's assertions, and the shared corpus's
|
|
65
|
+
// `ctx:spec:headers:authorization`. apidef writes `name: "Authorization"`,
|
|
66
|
+
// so emitting it verbatim would put the credential under a key none of
|
|
67
|
+
// those touch — a second header beside the one they manage rather than
|
|
68
|
+
// the one they manage. Header names are case-insensitive on the wire
|
|
69
|
+
// (RFC 7230) and lower case on HTTP/2, so nothing is lost. Query
|
|
70
|
+
// parameters and cookies ARE case-sensitive and keep the spec's spelling.
|
|
71
|
+
const resolved = resolveAuthName(model)
|
|
72
|
+
const name = 'header' === where ? resolved.toLowerCase() : resolved
|
|
73
|
+
|
|
74
|
+
const basic = isHttpBasicAuth(model)
|
|
75
|
+
|
|
76
|
+
// Read so the resolution is visible at generation time even though the
|
|
77
|
+
// emitted code takes the prefix from options at runtime (the secrets
|
|
78
|
+
// feature rewrites it there).
|
|
79
|
+
const prefix = resolveAuthPrefix(model)
|
|
80
|
+
|
|
81
|
+
// FOLDER NESTING, worked out from the template's own path and from
|
|
82
|
+
// Main_kotlin's call site.
|
|
83
|
+
//
|
|
84
|
+
// Main_kotlin's `Copy({ from: 'tm/kotlin', exclude: [/src\//] })` runs at
|
|
85
|
+
// Main's TOP LEVEL, so `tm/kotlin/utility/Prepare.kt` lands at
|
|
86
|
+
// `<out>/utility/Prepare.kt` — and `build.gradle.kts` declares
|
|
87
|
+
// `sourceSets["main"].kotlin.setSrcDirs(listOf("core", "utility",
|
|
88
|
+
// "feature", "entity"))`, four SIBLING roots at the target root. So this
|
|
89
|
+
// component opens exactly ONE segment, `utility`, and Main must call it
|
|
90
|
+
// from its top level — NOT from inside `Folder({ name: 'core' })`, where
|
|
91
|
+
// Config and SdkError are written.
|
|
92
|
+
//
|
|
93
|
+
// Nested there it would write `core/utility/PrepareAuth.kt`, and that
|
|
94
|
+
// mistake is INVISIBLE to a compile: `core` is itself a source root and
|
|
95
|
+
// gradle scans it recursively, so the misplaced file would still be
|
|
96
|
+
// compiled and its `package <pkg>.utility` declaration would still make
|
|
97
|
+
// `::prepareAuth` resolve. The tree would simply be wrong — a utility
|
|
98
|
+
// file filed under core — with nothing to report it.
|
|
99
|
+
Folder({ name: 'utility' }, () => {
|
|
100
|
+
File({ name: 'PrepareAuth.' + target.ext }, () => {
|
|
101
|
+
Content(render({
|
|
102
|
+
kotlinpackage,
|
|
103
|
+
suppressed: isAuthSuppressed(model),
|
|
104
|
+
where,
|
|
105
|
+
name,
|
|
106
|
+
basic,
|
|
107
|
+
prefix,
|
|
108
|
+
}))
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
type AuthSpec = {
|
|
115
|
+
kotlinpackage: string
|
|
116
|
+
suppressed: boolean
|
|
117
|
+
where: string
|
|
118
|
+
name: string
|
|
119
|
+
basic: boolean
|
|
120
|
+
prefix: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
function render(spec: AuthSpec): string {
|
|
125
|
+
// NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING.
|
|
126
|
+
//
|
|
127
|
+
// `isAuthActive` answers two different questions with one boolean:
|
|
128
|
+
//
|
|
129
|
+
// 1. `main.kit.config.auth.active: false` — this SDK is deliberately
|
|
130
|
+
// built WITHOUT auth. A per-SDK decision by the project.
|
|
131
|
+
// 2. `main.kit.info.auth: false` — the SPEC declares no
|
|
132
|
+
// security scheme. A fact about the API definition.
|
|
133
|
+
//
|
|
134
|
+
// Only (1) may silence prepareAuth. (2) says nothing about whether the
|
|
135
|
+
// CALLER holds a credential, and kotlin has always let one through: the
|
|
136
|
+
// optspec in `tm/kotlin/utility/MakeOptions.kt` supplies an
|
|
137
|
+
// `"auth": { "prefix": "" }` default whether or not the generated Config
|
|
138
|
+
// carries one, so `options["auth"]` is non-null at runtime, the
|
|
139
|
+
// template's `options["auth"] == null` guard never fired, and the
|
|
140
|
+
// credential was placed regardless of what the spec declared. The
|
|
141
|
+
// generated secrets feature depends on exactly that — it resolves a
|
|
142
|
+
// secret into `options.apikey` and expects prepareAuth to place it — and
|
|
143
|
+
// so does generatedcompile's `kotlin: auth null beats an explicit apikey`
|
|
144
|
+
// lane, whose baseline ("an ordinary apikey IS sent") is unsatisfiable
|
|
145
|
+
// the moment the wider gate is used.
|
|
146
|
+
//
|
|
147
|
+
// `isAuthSuppressed` is the narrow one: `main.kit.config.auth.active ===
|
|
148
|
+
// false` and nothing else. A spec that is merely silent keeps the
|
|
149
|
+
// credential path it has always had.
|
|
150
|
+
if (spec.suppressed) {
|
|
151
|
+
return head(spec) + `import ${spec.kotlinpackage}.core.Context
|
|
152
|
+
import ${spec.kotlinpackage}.core.Spec
|
|
153
|
+
|
|
154
|
+
// This SDK is built with \`config.auth.active: false\`, so there is no
|
|
155
|
+
// credential to place. The function stays in the pipeline because Register
|
|
156
|
+
// binds it unconditionally and MakeSpecUrl calls it on every request, and a
|
|
157
|
+
// missing spec is still the same error it always was.
|
|
158
|
+
fun prepareAuth(ctx: Context): Spec {
|
|
159
|
+
return ctx.spec
|
|
160
|
+
?: throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
161
|
+
}
|
|
162
|
+
`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ('query' === spec.where) return renderQuery(spec)
|
|
166
|
+
if ('cookie' === spec.where) return renderCookie(spec)
|
|
167
|
+
|
|
168
|
+
return renderHeader(spec)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
function head(spec: AuthSpec): string {
|
|
173
|
+
return `package ${spec.kotlinpackage}.utility
|
|
174
|
+
|
|
175
|
+
`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// The credential-absent test, identical in all three placements and
|
|
180
|
+
// identical to the template's. Kept as one string so a placement cannot
|
|
181
|
+
// drift from the others on what "no credential" means.
|
|
182
|
+
const SKIP = ` var skip = false
|
|
183
|
+
if (apikey == null) {
|
|
184
|
+
skip = true
|
|
185
|
+
} else if (apikey is String && (NOT_FOUND == apikey || "" == apikey)) {
|
|
186
|
+
skip = true
|
|
187
|
+
}
|
|
188
|
+
`
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
// HEADER. The body is the deleted \`fun prepareAuth\` from
|
|
192
|
+
// tm/kotlin/utility/Prepare.kt, character for character, for a scheme that
|
|
193
|
+
// resolves to the defaults (header / Authorization -> "authorization") and
|
|
194
|
+
// is not HTTP Basic. Only the constant's VALUE moves with the model, plus
|
|
195
|
+
// the Basic block, which is emitted only for a Basic scheme.
|
|
196
|
+
function renderHeader(spec: AuthSpec): string {
|
|
197
|
+
const basicBranch = spec.basic
|
|
198
|
+
|
|
199
|
+
return head(spec) + (basicBranch ? `import java.util.Base64
|
|
200
|
+
|
|
201
|
+
` : '') + `import ${spec.kotlinpackage}.core.Context
|
|
202
|
+
import ${spec.kotlinpackage}.core.Spec
|
|
203
|
+
import ${spec.kotlinpackage}.utility.struct.Struct
|
|
204
|
+
|
|
205
|
+
private const val HEADER_AUTH = "${ktstr(spec.name)}"
|
|
206
|
+
private const val OPTION_APIKEY = "apikey"
|
|
207
|
+
` + (basicBranch ? `private const val OPTION_SECRET = "secret"
|
|
208
|
+
` : '') + `private const val NOT_FOUND = "__NOTFOUND__"
|
|
209
|
+
|
|
210
|
+
fun prepareAuth(ctx: Context): Spec {
|
|
211
|
+
val spec = ctx.spec
|
|
212
|
+
?: throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
213
|
+
|
|
214
|
+
val headers = spec.headers
|
|
215
|
+
val options = ctx.client!!.optionsMap()
|
|
216
|
+
|
|
217
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
218
|
+
if (options["auth"] == null) {
|
|
219
|
+
headers.remove(HEADER_AUTH)
|
|
220
|
+
return spec
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
224
|
+
` + basicBlock(spec) + `
|
|
225
|
+
` + SKIP + `
|
|
226
|
+
if (skip) {
|
|
227
|
+
headers.remove(HEADER_AUTH)
|
|
228
|
+
} else {
|
|
229
|
+
var authPrefix = ""
|
|
230
|
+
val ap = Struct.getpath(options, listOf("auth", "prefix"))
|
|
231
|
+
if (ap is String) {
|
|
232
|
+
authPrefix = ap
|
|
233
|
+
}
|
|
234
|
+
val apikeyVal = if (apikey is String) apikey else ""
|
|
235
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
236
|
+
if ("" == authPrefix) {
|
|
237
|
+
headers[HEADER_AUTH] = apikeyVal
|
|
238
|
+
} else {
|
|
239
|
+
headers[HEADER_AUTH] = "\$authPrefix \$apikeyVal"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return spec
|
|
244
|
+
}
|
|
245
|
+
`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
// HTTP BASIC IS HEADER-ONLY BY DEFINITION: the scheme is
|
|
250
|
+
// `Authorization: Basic base64(user:pass)`. It cannot be expressed as a
|
|
251
|
+
// query parameter or a cookie, so the branch is emitted only from
|
|
252
|
+
// renderHeader, and only when the model says this API actually uses it — a
|
|
253
|
+
// bearer SDK carries none of it.
|
|
254
|
+
//
|
|
255
|
+
// `Struct.getpath` returns the UNDEF sentinel, not null, for a path the map
|
|
256
|
+
// does not have, so the switch is read with `is Boolean` rather than
|
|
257
|
+
// compared against `true`: the sentinel is neither.
|
|
258
|
+
function basicBlock(spec: AuthSpec): string {
|
|
259
|
+
if (!spec.basic) return ''
|
|
260
|
+
|
|
261
|
+
return `
|
|
262
|
+
// True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
263
|
+
// token in the header (the branch below) can never authenticate against
|
|
264
|
+
// an API that actually checks \`Authorization: Basic base64(user:pass)\`.
|
|
265
|
+
val basicOpt = Struct.getpath(options, listOf("auth", "basic"))
|
|
266
|
+
if (basicOpt is Boolean && basicOpt) {
|
|
267
|
+
val secret = Struct.getprop(options, OPTION_SECRET, NOT_FOUND)
|
|
268
|
+
val noApikey = apikey == null ||
|
|
269
|
+
(apikey is String && (NOT_FOUND == apikey || "" == apikey))
|
|
270
|
+
val noSecret = secret == null ||
|
|
271
|
+
(secret is String && (NOT_FOUND == secret || "" == secret))
|
|
272
|
+
|
|
273
|
+
if (noApikey || noSecret) {
|
|
274
|
+
headers.remove(HEADER_AUTH)
|
|
275
|
+
} else {
|
|
276
|
+
var basicPrefix = ""
|
|
277
|
+
val bp = Struct.getpath(options, listOf("auth", "prefix"))
|
|
278
|
+
if (bp is String) {
|
|
279
|
+
basicPrefix = bp
|
|
280
|
+
}
|
|
281
|
+
val b64 = Base64.getEncoder().encodeToString(
|
|
282
|
+
(apikey.toString() + ":" + secret.toString()).toByteArray(Charsets.UTF_8))
|
|
283
|
+
if ("" == basicPrefix) {
|
|
284
|
+
headers[HEADER_AUTH] = b64
|
|
285
|
+
} else {
|
|
286
|
+
headers[HEADER_AUTH] = "\$basicPrefix \$b64"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return spec
|
|
291
|
+
}
|
|
292
|
+
`
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
// QUERY. MakeSpecUrl fills `spec.query` (prepareQuery) BEFORE it calls
|
|
297
|
+
// prepareAuth, and makeUrl reads `spec.query` afterwards, url-escaping every
|
|
298
|
+
// key and value onto the URL — so the credential placed here reaches the
|
|
299
|
+
// wire as `?name=value`. The headers are never touched.
|
|
300
|
+
function renderQuery(spec: AuthSpec): string {
|
|
301
|
+
return head(spec) + `import ${spec.kotlinpackage}.core.Context
|
|
302
|
+
import ${spec.kotlinpackage}.core.Spec
|
|
303
|
+
import ${spec.kotlinpackage}.utility.struct.Struct
|
|
304
|
+
|
|
305
|
+
private const val QUERY_AUTH = "${ktstr(spec.name)}"
|
|
306
|
+
private const val OPTION_APIKEY = "apikey"
|
|
307
|
+
private const val NOT_FOUND = "__NOTFOUND__"
|
|
308
|
+
|
|
309
|
+
fun prepareAuth(ctx: Context): Spec {
|
|
310
|
+
val spec = ctx.spec
|
|
311
|
+
?: throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
312
|
+
|
|
313
|
+
val query = spec.query
|
|
314
|
+
val options = ctx.client!!.optionsMap()
|
|
315
|
+
|
|
316
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
317
|
+
if (options["auth"] == null) {
|
|
318
|
+
query.remove(QUERY_AUTH)
|
|
319
|
+
return spec
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
323
|
+
|
|
324
|
+
` + SKIP + `
|
|
325
|
+
if (skip) {
|
|
326
|
+
query.remove(QUERY_AUTH)
|
|
327
|
+
} else {
|
|
328
|
+
val apikeyVal = if (apikey is String) apikey else ""
|
|
329
|
+
// NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
|
|
330
|
+
// thing any API reads; the prefix is a header convention, so
|
|
331
|
+
// options.auth.prefix is dropped here deliberately rather than silently
|
|
332
|
+
// concatenated.
|
|
333
|
+
query[QUERY_AUTH] = apikeyVal
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return spec
|
|
337
|
+
}
|
|
338
|
+
`
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
// COOKIE. A cookie IS a header, so the credential rides the header bag —
|
|
343
|
+
// but the `cookie` header is SHARED with whatever cookies options.headers
|
|
344
|
+
// (or an earlier feature) put there, so our pair is SPLICED in and out
|
|
345
|
+
// rather than the header assigned over. Splicing also makes this
|
|
346
|
+
// idempotent: a retried request cannot end up carrying the credential
|
|
347
|
+
// twice.
|
|
348
|
+
function renderCookie(spec: AuthSpec): string {
|
|
349
|
+
return head(spec) + `import ${spec.kotlinpackage}.core.Context
|
|
350
|
+
import ${spec.kotlinpackage}.core.Spec
|
|
351
|
+
import ${spec.kotlinpackage}.utility.struct.Struct
|
|
352
|
+
|
|
353
|
+
private const val COOKIE_HEADER = "cookie"
|
|
354
|
+
private const val COOKIE_AUTH = "${ktstr(spec.name)}"
|
|
355
|
+
private const val OPTION_APIKEY = "apikey"
|
|
356
|
+
private const val NOT_FOUND = "__NOTFOUND__"
|
|
357
|
+
|
|
358
|
+
// The cookie header minus our own pair, every other cookie untouched.
|
|
359
|
+
private fun cookiesWithoutCred(headers: MutableMap<String, Any?>): String {
|
|
360
|
+
val existing = headers[COOKIE_HEADER]
|
|
361
|
+
if (existing !is String || "" == existing) {
|
|
362
|
+
return ""
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
val kept = mutableListOf<String>()
|
|
366
|
+
for (part in existing.split(";")) {
|
|
367
|
+
val piece = part.trim()
|
|
368
|
+
if ("" == piece || COOKIE_AUTH == piece || piece.startsWith("\$COOKIE_AUTH=")) {
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
kept.add(piece)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return kept.joinToString("; ")
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Set (a value) or remove (null) our pair, leaving every other cookie in
|
|
378
|
+
// place.
|
|
379
|
+
private fun applyCookie(headers: MutableMap<String, Any?>, value: String?) {
|
|
380
|
+
val rest = cookiesWithoutCred(headers)
|
|
381
|
+
|
|
382
|
+
if (value == null) {
|
|
383
|
+
if ("" == rest) {
|
|
384
|
+
headers.remove(COOKIE_HEADER)
|
|
385
|
+
} else {
|
|
386
|
+
headers[COOKIE_HEADER] = rest
|
|
387
|
+
}
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
val pair = "\$COOKIE_AUTH=\$value"
|
|
392
|
+
headers[COOKIE_HEADER] = if ("" == rest) pair else "\$rest; \$pair"
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fun prepareAuth(ctx: Context): Spec {
|
|
396
|
+
val spec = ctx.spec
|
|
397
|
+
?: throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
398
|
+
|
|
399
|
+
val headers = spec.headers
|
|
400
|
+
val options = ctx.client!!.optionsMap()
|
|
401
|
+
|
|
402
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
403
|
+
if (options["auth"] == null) {
|
|
404
|
+
applyCookie(headers, null)
|
|
405
|
+
return spec
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
409
|
+
|
|
410
|
+
` + SKIP + `
|
|
411
|
+
if (skip) {
|
|
412
|
+
applyCookie(headers, null)
|
|
413
|
+
} else {
|
|
414
|
+
val apikeyVal = if (apikey is String) apikey else ""
|
|
415
|
+
// NO PREFIX IN A COOKIE either - a cookie carries a bare \`name=value\`
|
|
416
|
+
// pair, not a header's scheme-prefixed credential.
|
|
417
|
+
applyCookie(headers, apikeyVal)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return spec
|
|
421
|
+
}
|
|
422
|
+
`
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
// A Kotlin double-quoted string literal body. These names come from the
|
|
427
|
+
// API's own securityScheme, so they are not guaranteed to be bare
|
|
428
|
+
// identifiers — and `$` has to go too, because Kotlin reads it as a string
|
|
429
|
+
// template introducer rather than as a dollar sign.
|
|
430
|
+
function ktstr(s: string): string {
|
|
431
|
+
return String(s)
|
|
432
|
+
.replace(/\\/g, '\\\\')
|
|
433
|
+
.replace(/"/g, '\\"')
|
|
434
|
+
.replace(/\$/g, '\\$')
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
export {
|
|
439
|
+
PrepareAuth
|
|
440
|
+
}
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
each,
|
|
14
14
|
isAuthActive,
|
|
15
15
|
isConfigData,
|
|
16
|
+
resolveAuthIn,
|
|
17
|
+
resolveAuthName,
|
|
16
18
|
resolveAuthPrefix,
|
|
17
19
|
serverVariables,
|
|
18
20
|
targetFeatures,
|
|
@@ -114,9 +116,21 @@ const Config = cmp(async function Config(props: any) {
|
|
|
114
116
|
svars.map((v: any) => ` [${JSON.stringify(v.name)}] = ${JSON.stringify(v.dflt)},\n`).join('') +
|
|
115
117
|
' },\n'
|
|
116
118
|
|
|
119
|
+
// WHERE the credential goes and UNDER WHAT NAME. apidef resolved both
|
|
120
|
+
// from the spec's securityScheme all along and generation dropped them,
|
|
121
|
+
// so an apiKey-in-query API got an authorization header it does not read.
|
|
122
|
+
// Emitted only when they differ from the defaults, so a
|
|
123
|
+
// header/Authorization SDK is byte-identical to what it generated before.
|
|
124
|
+
//
|
|
125
|
+
// `in` is a RESERVED WORD in lua, so the key is bracketed - a bare
|
|
126
|
+
// `in = "query"` does not parse.
|
|
127
|
+
const authIn = resolveAuthIn(model)
|
|
128
|
+
const authName = resolveAuthName(model)
|
|
117
129
|
const authBlock = authActive
|
|
118
130
|
? ` auth = {
|
|
119
|
-
prefix = "${authPrefix}"
|
|
131
|
+
prefix = "${authPrefix}",${'header' === authIn ? '' : `
|
|
132
|
+
["in"] = "${authIn}",`}${'Authorization' === authName ? '' : `
|
|
133
|
+
name = "${authName}",`}
|
|
120
134
|
},\n`
|
|
121
135
|
: ''
|
|
122
136
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
|
|
23
23
|
import { Package } from './Package_lua'
|
|
24
24
|
import { Config } from './Config_lua'
|
|
25
|
+
import { PrepareAuth } from './PrepareAuth_lua'
|
|
25
26
|
import { Gitignore } from './Gitignore_lua'
|
|
26
27
|
import { MainEntity } from './MainEntity_lua'
|
|
27
28
|
import { EntityTypes } from './EntityTypes_lua'
|
|
@@ -195,6 +196,16 @@ self._utility.feature_hook(self._rootctx, "${name}")
|
|
|
195
196
|
Config({ target })
|
|
196
197
|
})
|
|
197
198
|
|
|
199
|
+
// GENERATED, NOT COPIED. Where the credential goes is a fact about the
|
|
200
|
+
// API - header, query or cookie, under the name the spec gives - and tm/
|
|
201
|
+
// can only hold one answer. See PrepareAuth_lua.
|
|
202
|
+
//
|
|
203
|
+
// Called at the TARGET ROOT, like Config above (that Folder({name:'.'})
|
|
204
|
+
// is the root itself), because the lua tree has no src/ wrapper: the
|
|
205
|
+
// component opens the one `utility` folder that
|
|
206
|
+
// require("utility.prepare_auth") needs.
|
|
207
|
+
PrepareAuth({ target })
|
|
208
|
+
|
|
198
209
|
// Generate feature factory module
|
|
199
210
|
File({ name: 'features.' + target.ext }, () => {
|
|
200
211
|
Content(`-- ${model.const.Name} SDK feature factory
|