@voxgig/sdkgen 4.10.0 → 4.11.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/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +330 -0
- package/project/.sdk/model/target/c.aon +15 -2
- package/project/.sdk/model/target/cpp.aon +18 -0
- package/project/.sdk/model/target/lua.aon +17 -0
- package/project/.sdk/model/target/ocaml.aon +48 -5
- package/project/.sdk/model/target/zig.aon +30 -4
- package/project/.sdk/src/cmp/c/Config_c.ts +338 -1
- package/project/.sdk/src/cmp/c/Main_c.ts +57 -2
- package/project/.sdk/src/cmp/c/Package_c.ts +12 -2
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +324 -2
- package/project/.sdk/src/cmp/cpp/Gitignore_cpp.ts +2 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +62 -4
- package/project/.sdk/src/cmp/cpp/Package_cpp.ts +10 -0
- package/project/.sdk/src/cmp/lua/Config_lua.ts +88 -0
- package/project/.sdk/src/cmp/lua/Gitignore_lua.ts +4 -0
- package/project/.sdk/src/cmp/lua/Main_lua.ts +93 -3
- package/project/.sdk/src/cmp/lua/Package_lua.ts +60 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +10 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +183 -5
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +160 -3
- package/project/.sdk/src/cmp/ocaml/Package_ocaml.ts +25 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +130 -9
- package/project/.sdk/src/cmp/zig/Main_zig.ts +208 -4
- package/project/.sdk/{tm/zig/build.zig → src/cmp/zig/fragment/Build.fragment.zig} +7 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +25 -0
- package/project/.sdk/{tm/zig/root.zig → src/cmp/zig/fragment/Root.fragment.zig} +9 -0
- package/project/.sdk/tm/c/Makefile +58 -9
- package/project/.sdk/tm/c/core/sdk.h +10 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/capability.c +133 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/capability.h +39 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/catalog.c +83 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/catalog.h +48 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/config.c +413 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/config.h +24 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/depend.c +264 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/depend.h +29 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/env.c +220 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/env.h +19 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/export.c +97 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/export.h +19 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/graph.c +237 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/graph.h +17 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/host.c +1494 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/host.h +135 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/order.c +288 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/order.h +18 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/point.c +141 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/point.h +80 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/ref.c +170 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/ref.h +52 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/resolve.c +98 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/resolve.h +18 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/types.c +150 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/types.h +104 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/value.c +649 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/value.h +135 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/version.c +151 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/version.h +34 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/aws.c +281 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/azuresecrets.c +207 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/boru.c +164 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/clock.c +75 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/doppler.c +141 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/encode.c +144 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/gcpsecrets.c +177 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/hashicorp.c +239 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/httpjson.c +599 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/infisical.c +165 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/onepassword.c +194 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/proc.c +233 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/secretspec.c +125 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/sekretoplugins.h +125 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/sha256.c +270 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/sigv4.c +382 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/support.h +153 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/tls.c +420 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/internal.h +86 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/json.c +721 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/providers.c +813 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/sekreto.c +944 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/sekreto.h +490 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/util.c +442 -0
- package/project/.sdk/tm/c/feature/secrets.c +797 -0
- package/project/.sdk/tm/c/feature/secrets.h +46 -0
- package/project/.sdk/tm/c/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/c/tests/feature/secrets/secrets_test.c +802 -0
- package/project/.sdk/tm/clojure/feature/secrets/sdk/feature/secrets.clj +38 -11
- package/project/.sdk/tm/clojure/test/sdk/test/feature/secrets.clj +40 -6
- package/project/.sdk/tm/cpp/Makefile +48 -4
- package/project/.sdk/tm/cpp/feature/secrets/plugin/capability.cpp +121 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/capability.hpp +41 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/catalog.cpp +60 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/catalog.hpp +66 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/config.cpp +376 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/config.hpp +29 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/depend.cpp +230 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/depend.hpp +31 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/env.cpp +205 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/env.hpp +25 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/export.cpp +84 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/export.hpp +24 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/graph.cpp +222 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/graph.hpp +21 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/host.cpp +1200 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/host.hpp +250 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/order.cpp +250 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/order.hpp +22 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/point.cpp +110 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/point.hpp +79 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/ref.cpp +136 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/ref.hpp +60 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/resolve.cpp +82 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/resolve.hpp +25 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/types.cpp +90 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/types.hpp +65 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/value.cpp +491 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/value.hpp +120 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/version.cpp +143 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/version.hpp +36 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Aws.cpp +248 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Aws.hpp +30 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Azuresecrets.cpp +149 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Azuresecrets.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Boru.cpp +117 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Boru.hpp +27 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Crypto.cpp +180 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Crypto.hpp +52 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Doppler.cpp +87 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Doppler.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Gcpsecrets.cpp +121 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Gcpsecrets.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Hashicorp.cpp +153 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Hashicorp.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Httpjson.cpp +666 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Httpjson.hpp +160 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Infisical.cpp +123 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Infisical.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Onepassword.cpp +125 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Onepassword.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Proc.cpp +223 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Proc.hpp +47 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Secretspec.cpp +104 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Secretspec.hpp +27 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Sigv4.cpp +242 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Sigv4.hpp +58 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Tls.cpp +260 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Tls.hpp +47 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Json.cpp +476 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Json.hpp +89 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Provider.cpp +312 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Provider.hpp +263 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Providers.cpp +354 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Providers.hpp +111 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Sekreto.cpp +613 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Sekreto.hpp +227 -0
- package/project/.sdk/tm/cpp/feature/secrets.hpp +745 -0
- package/project/.sdk/tm/cpp/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/cpp/test/feature/secrets/secrets_test.cpp +808 -0
- package/project/.sdk/tm/csharp/feature/SecretsFeature.cs +34 -8
- package/project/.sdk/tm/csharp/test/feature/secrets/SecretsFeatureTest.cs +85 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/secrets.ex +17 -2
- package/project/.sdk/tm/elixir/test/feature/secrets/secrets_test.exs +41 -0
- package/project/.sdk/tm/go/feature/secrets_feature.go +32 -10
- package/project/.sdk/tm/go/test/feature/secrets/secrets_feature_test.go +116 -0
- package/project/.sdk/tm/java/feature/SecretsFeature.java +33 -8
- package/project/.sdk/tm/java/test/feature/secrets/SecretsFeatureTest.java +64 -0
- package/project/.sdk/tm/js/src/feature/secrets/SecretsFeature.js +34 -11
- package/project/.sdk/tm/js/test/feature/secrets/Secrets.test.js +73 -0
- package/project/.sdk/tm/kotlin/feature/SecretsFeature.kt +32 -7
- package/project/.sdk/tm/kotlin/test/feature/secrets/SecretsTest.kt +69 -0
- package/project/.sdk/tm/lua/Makefile +15 -4
- package/project/.sdk/tm/lua/feature/secrets/native/sekretonet.c +806 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/capability.lua +135 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/catalog.lua +63 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/config.lua +361 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/depend.lua +232 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/env.lua +168 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/export.lua +66 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/graph.lua +194 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/host.lua +1190 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/json.lua +169 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/order.lua +207 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/point.lua +106 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/ref.lua +115 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/resolve.lua +78 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/types.lua +275 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/version.lua +131 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin.lua +76 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/addr.lua +124 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/err.lua +47 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/name.lua +315 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/aws.lua +228 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/azuresecrets.lua +137 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/boru.lua +124 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/crypto.lua +212 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/doppler.lua +89 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/gcpsecrets.lua +129 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/hashicorp.lua +154 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/httpjson.lua +248 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/infisical.lua +113 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/json.lua +449 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/net.lua +198 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/onepassword.lua +126 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/secretspec.lua +93 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/sigv4.lua +269 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/support.lua +146 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/providers.lua +346 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto.lua +479 -0
- package/project/.sdk/tm/lua/feature/secrets_feature.lua +690 -0
- package/project/.sdk/tm/lua/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/lua/test/extend_test.lua +105 -0
- package/project/.sdk/tm/lua/test/feature/secrets/secrets_feature_test.lua +862 -0
- package/project/.sdk/tm/ocaml/Makefile +52 -8
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/capability.ml +106 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/catalog.ml +43 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/config.ml +349 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/defs.ml +143 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/depend.ml +217 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/env.ml +197 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/export.ml +89 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/graph.ml +227 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/host.ml +1112 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/order.ml +202 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/point.ml +138 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/ref.ml +126 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/resolve.ml +62 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/types.ml +97 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/value.ml +387 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/version.ml +139 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/aws.ml +171 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/azuresecrets.ml +116 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/boru.ml +95 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/crypto.ml +149 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/doppler.ml +61 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/gcpsecrets.ml +93 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/hashicorp.ml +120 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/http.ml +421 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/httpjson.ml +66 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/infisical.ml +85 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/onepassword.ml +112 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/runcmd.ml +97 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/secretspec.ml +68 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/sigv4.ml +259 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/tls.ml +44 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/tls_stubs.c +358 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/json.ml +376 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/provider.ml +647 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/secret.ml +299 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/sekreto.ml +347 -0
- package/project/.sdk/tm/ocaml/feature/secrets_feature.ml +453 -0
- package/project/.sdk/tm/ocaml/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +670 -0
- package/project/.sdk/tm/perl/feature/secrets_feature.pm +33 -12
- package/project/.sdk/tm/perl/t/feature/secrets/secrets.t +47 -0
- package/project/.sdk/tm/php/feature/SecretsFeature.php +30 -6
- package/project/.sdk/tm/php/test/feature/secrets/SecretsTest.php +98 -0
- package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +28 -6
- package/project/.sdk/tm/py/test/feature/secrets/test_secrets.py +114 -0
- package/project/.sdk/tm/rb/feature/secrets_feature.rb +28 -6
- package/project/.sdk/tm/rb/test/feature/secrets/secrets_feature_test.rb +93 -0
- package/project/.sdk/tm/rust/feature/secrets.rs +33 -10
- package/project/.sdk/tm/rust/tests/feature/secrets/main.rs +96 -0
- package/project/.sdk/tm/scala/feature/SecretsFeature.scala +29 -7
- package/project/.sdk/tm/scala/sdktest/feature/secrets/SecretsTestMain.scala +71 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +35 -12
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +73 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/capability.zig +137 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/catalog.zig +90 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/config.zig +346 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/depend.zig +244 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/env.zig +201 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/export.zig +83 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/graph.zig +212 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/host.zig +1224 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/inst.zig +126 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/order.zig +228 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/plugin.zig +34 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/point.zig +166 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/ref.zig +147 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/resolve.zig +74 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/types.zig +140 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/value.zig +662 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/version.zig +139 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/aws.zig +294 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/azuresecrets.zig +222 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/boru.zig +188 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/doppler.zig +135 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/gcpsecrets.zig +170 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/hashicorp.zig +242 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/httpjson.zig +535 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/infisical.zig +167 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/onepassword.zig +202 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/secretspec.zig +160 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/sigv4.zig +336 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/addr.zig +148 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/builtins.zig +259 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/provider.zig +440 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/sekreto.zig +944 -0
- package/project/.sdk/tm/zig/feature/secrets.zig +854 -0
- package/project/.sdk/tm/zig/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/zig/test/feature/secrets/secrets_test.zig +891 -0
- package/project/sdkgen-package.json +1 -1
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
// Secret access via a vendored @voxgig/sekreto provider chain, and the
|
|
2
|
+
// access-token exchange some APIs require on top of it. The zig port of
|
|
3
|
+
// tm/ts/src/feature/secrets/SecretsFeature.ts, following the GO port's
|
|
4
|
+
// structure (feature/secrets_feature.go) - same contract, zig idiom.
|
|
5
|
+
//
|
|
6
|
+
// The SDK's `apikey` option keeps exactly its old meaning: an explicit
|
|
7
|
+
// credential given in code. This feature makes it ONE SOURCE among several
|
|
8
|
+
// rather than the only one: when active, the apikey is resolved through a
|
|
9
|
+
// sekreto chain in which the explicit option (when set) is the FIRST
|
|
10
|
+
// provider - a `memory` store named `options` - so an explicit value always
|
|
11
|
+
// wins, by sekreto's own first-hit rule rather than by special-case logic.
|
|
12
|
+
// When the option is unset the remaining providers (env, dotenv, a vault)
|
|
13
|
+
// are asked in order, and moving a credential from code to a vault becomes
|
|
14
|
+
// a configuration change.
|
|
15
|
+
//
|
|
16
|
+
// MISS vs ERROR (sekreto's invariant, and STRUCTURAL in this port: a lookup
|
|
17
|
+
// answers `.ok = null` for a miss and `.err = message` for a failure): a
|
|
18
|
+
// provider MISS falls through - the op proceeds, unauthenticated if nothing
|
|
19
|
+
// else supplies a credential. A provider ERROR (unreachable vault, bad
|
|
20
|
+
// creds) must FAIL the op: a broken vault never degrades into an
|
|
21
|
+
// unauthenticated request.
|
|
22
|
+
//
|
|
23
|
+
// WHERE RESOLUTION HAPPENS, and why it is not the PreSpec hook. zig's
|
|
24
|
+
// `Feature.VTable.dispatch` returns void (core/types.zig), so a hook cannot
|
|
25
|
+
// fail an operation the way ts's awaited hook rejection can - and `direct()`
|
|
26
|
+
// / `graphql()` run no feature hooks at all (Main.fragment.zig raw_request
|
|
27
|
+
// calls sdkUtility.fetch directly). So, exactly as in the go and rust ports,
|
|
28
|
+
// resolution happens at the TRANSPORT SEAM: the one place every wire path
|
|
29
|
+
// crosses. The wrapper refuses to send while the last resolution stands
|
|
30
|
+
// failed, which is fail-closed for the entity pipeline and the raw paths
|
|
31
|
+
// alike, with one implementation. The wrapper is installed FIRST, before the
|
|
32
|
+
// chain is built, so a construction failure of any kind still leaves the
|
|
33
|
+
// gate in place.
|
|
34
|
+
//
|
|
35
|
+
// WHERE THE CREDENTIAL LIVES, and why it is not the options map. The ts
|
|
36
|
+
// reference writes `options.apikey`; this port does NOT. `options_map()`
|
|
37
|
+
// deep-clones on every prepare_auth call and the root options Value is
|
|
38
|
+
// shared by every context, so a feature that mutated it would be visible
|
|
39
|
+
// everywhere at once and would defeat the `auth: null` suppression pin. The
|
|
40
|
+
// resolved credential is held in FEATURE STATE and written into each
|
|
41
|
+
// request's header at the transport, the same construction prepare_auth
|
|
42
|
+
// uses (options.auth.prefix), so the wire is identical and the options map
|
|
43
|
+
// stays frozen after construction.
|
|
44
|
+
//
|
|
45
|
+
// EXCHANGE: some APIs will not take a long-lived credential at all. What
|
|
46
|
+
// the chain resolves is then a REFRESH token, which buys a short-lived
|
|
47
|
+
// ACCESS token from a token endpoint (`exchange.path`, relative to
|
|
48
|
+
// options.base); the access token is what every request carries, and when
|
|
49
|
+
// a response status in `exchange.statuses` (401) says it is spent the
|
|
50
|
+
// wrapper buys another and retries the same request once. Test mode buys
|
|
51
|
+
// nothing and answers with a deterministic fake token.
|
|
52
|
+
//
|
|
53
|
+
// THE EXCHANGE'S TRANSPORT OF LAST RESORT. The SDK's own default_http_fetch
|
|
54
|
+
// (core/utility.zig) has no live HTTP client - a real deployment injects
|
|
55
|
+
// `system.fetch`. The exchange uses `system.fetch` when there is one; when
|
|
56
|
+
// there is not, it uses the vendored sekreto plugins' own HTTP round-trip
|
|
57
|
+
// (`sekretoplugins.httpjson.fetchjson`: std.http.Client + std.crypto.tls,
|
|
58
|
+
// no external dependency), which the generated feature/secrets/plugins.zig
|
|
59
|
+
// always exports. That keeps the HTTP transport INSIDE the gated feature:
|
|
60
|
+
// an SDK without secrets links none of it. Deliberately NOT the SDK
|
|
61
|
+
// transport, which this feature wraps - sending the token request back
|
|
62
|
+
// through it would recurse on the first expiry.
|
|
63
|
+
//
|
|
64
|
+
// THE ENVIRONMENT. zig 0.16 removed std.os.environ: a library cannot read
|
|
65
|
+
// the process environment by itself, and sekreto's Config asks for the map.
|
|
66
|
+
// The SDK builds it from the block std's startup code hands to
|
|
67
|
+
// `std.Io.Threaded.global_single_threaded` (core/mem.zig's io) - present in
|
|
68
|
+
// every build mode and needing no libc - and an application that holds
|
|
69
|
+
// `init.environ_map` can hand its own in through `SecretsFeature
|
|
70
|
+
// .set_environ` before constructing the client (the test suite does).
|
|
71
|
+
//
|
|
72
|
+
// THREADING: the zig SDK is single-threaded by construction (one process
|
|
73
|
+
// arena, one Utility mutated in place by every wrapping feature), so go's
|
|
74
|
+
// mutex/channel machinery for shared in-flight resolutions and purchases
|
|
75
|
+
// collapses to plain fields here, as it does in rust.
|
|
76
|
+
//
|
|
77
|
+
// Every refusal this feature raises carries the error code `secrets`, so a
|
|
78
|
+
// caller can tell a secrets failure from a transport failure.
|
|
79
|
+
|
|
80
|
+
const std = @import("std");
|
|
81
|
+
const h = @import("../core/helpers.zig");
|
|
82
|
+
const mem = @import("../core/mem.zig");
|
|
83
|
+
const err = @import("../core/error.zig");
|
|
84
|
+
const types = @import("../core/types.zig");
|
|
85
|
+
const sup = @import("support.zig");
|
|
86
|
+
|
|
87
|
+
// NAMED BUILD MODULES, not paths: the vendored trees under feature/secrets/
|
|
88
|
+
// are the roots of the `sekreto` and `sekretoplugins` modules build.zig
|
|
89
|
+
// declares (Main_zig FEATURE_MODULES) - only when this feature is active,
|
|
90
|
+
// which is also the only time anything analyses this file. A path import
|
|
91
|
+
// would put the vendored files in two modules at once ("file exists in
|
|
92
|
+
// multiple modules").
|
|
93
|
+
const sekreto = @import("sekreto");
|
|
94
|
+
const sekplugins = @import("sekretoplugins");
|
|
95
|
+
|
|
96
|
+
const Value = h.Value;
|
|
97
|
+
const Allocator = std.mem.Allocator;
|
|
98
|
+
const Context = types.Context;
|
|
99
|
+
const Feature = types.Feature;
|
|
100
|
+
const Fetcher = types.Fetcher;
|
|
101
|
+
const E = err.E;
|
|
102
|
+
|
|
103
|
+
const pv = sekreto.plugin.value;
|
|
104
|
+
const Environ = std.process.Environ;
|
|
105
|
+
|
|
106
|
+
const ERRCODE = "secrets";
|
|
107
|
+
const HEADER_AUTH = "authorization";
|
|
108
|
+
|
|
109
|
+
/// The access-token exchange, normalised once at init. Null when off, so
|
|
110
|
+
/// every later decision is one optional check.
|
|
111
|
+
const Exchange = struct {
|
|
112
|
+
path: []const u8,
|
|
113
|
+
method: []const u8,
|
|
114
|
+
request: []const u8,
|
|
115
|
+
response: []const u8,
|
|
116
|
+
statuses: []const i64,
|
|
117
|
+
retries: i64,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/// Everything the transport wrapper needs, heap-allocated once per feature
|
|
121
|
+
/// so the wrapper's closure context and the feature share it.
|
|
122
|
+
pub const State = struct {
|
|
123
|
+
/// The root context this feature was initialised against: its `client`
|
|
124
|
+
/// carries the mode (test mode buys nothing) and its `options` are the
|
|
125
|
+
/// resolved live options, READ-ONLY here - see the header note.
|
|
126
|
+
rootctx: ?*Context = null,
|
|
127
|
+
liveopts: Value = .{ .null = {} },
|
|
128
|
+
|
|
129
|
+
secretname: []const u8 = "apikey",
|
|
130
|
+
cache: bool = true,
|
|
131
|
+
exchange: ?Exchange = null,
|
|
132
|
+
|
|
133
|
+
/// The LIVE chain, for callers who want arbitrary secrets or redaction.
|
|
134
|
+
sek: ?*sekreto.Sekreto = null,
|
|
135
|
+
|
|
136
|
+
/// The environment map the chain's `env` provider reads. Owned here so
|
|
137
|
+
/// the `Config` pointer into it stays valid for the client's life.
|
|
138
|
+
envmap: Environ.Map = undefined,
|
|
139
|
+
|
|
140
|
+
/// The RESOLVED credential, injected into each request at the transport
|
|
141
|
+
/// seam - never written into the shared options map.
|
|
142
|
+
cred: []const u8 = "",
|
|
143
|
+
/// The refresh token the chain resolved, when exchanging.
|
|
144
|
+
refresh: []const u8 = "",
|
|
145
|
+
|
|
146
|
+
/// A settled SUCCESSFUL resolution. Kept only while caching is on
|
|
147
|
+
/// (`cache: false` means every request asks the chain again), and never
|
|
148
|
+
/// set by a FAILURE - a transient vault outage must not poison the
|
|
149
|
+
/// client permanently.
|
|
150
|
+
resolved: bool = false,
|
|
151
|
+
|
|
152
|
+
/// A chain that could not be built. The transport gate refuses to send
|
|
153
|
+
/// while this stands, which keeps a misconfigured chain fail-closed
|
|
154
|
+
/// rather than silently unauthenticated.
|
|
155
|
+
initerr: ?[]const u8 = null,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
pub const SecretsFeature = struct {
|
|
159
|
+
name: []const u8 = "secrets",
|
|
160
|
+
active: bool = true,
|
|
161
|
+
add_opts: Value = .{ .null = {} },
|
|
162
|
+
options: Value = .{ .null = {} },
|
|
163
|
+
state: *State,
|
|
164
|
+
|
|
165
|
+
pub fn make() Feature {
|
|
166
|
+
const self = h.A().create(SecretsFeature) catch unreachable;
|
|
167
|
+
const state = h.A().create(State) catch unreachable;
|
|
168
|
+
state.* = .{};
|
|
169
|
+
self.* = .{ .state = state };
|
|
170
|
+
return .{ .ptr = @ptrCast(self), .vtable = &vtable };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// The process environment the `env` provider reads, handed in by the
|
|
174
|
+
/// application - a zig 0.16 `main(init)` holds it as `init.environ_map`.
|
|
175
|
+
/// Null (the default) means the SDK builds one itself from std's startup
|
|
176
|
+
/// block. Read at the NEXT construction: set it before `new()`.
|
|
177
|
+
pub fn set_environ(map: ?*const Environ.Map) void {
|
|
178
|
+
given_environ = map;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// The resolved credential (empty when none) - the state the transport
|
|
182
|
+
/// injects. Read here rather than from the options map, which this
|
|
183
|
+
/// feature never mutates.
|
|
184
|
+
pub fn credential(self: *SecretsFeature) []const u8 {
|
|
185
|
+
return self.state.cred;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// The construction failure the gate is refusing on, if any.
|
|
189
|
+
pub fn init_error(self: *SecretsFeature) ?[]const u8 {
|
|
190
|
+
return self.state.initerr;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// The LIVE sekreto instance (the zig spelling of ts's public sekreto()
|
|
194
|
+
/// accessor). Never a clone: it holds provider state and the cache.
|
|
195
|
+
pub fn sek(self: *SecretsFeature) ?*sekreto.Sekreto {
|
|
196
|
+
return self.state.sek;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// A transparent read through the chain: the value, or sekreto's own
|
|
200
|
+
/// message (a miss is `sekreto: unknown secret: <name>`).
|
|
201
|
+
pub fn get(self: *SecretsFeature, name: []const u8) sekreto.Answer([]const u8) {
|
|
202
|
+
const s = self.state.sek orelse return .{ .err = "secrets: no provider chain" };
|
|
203
|
+
return s.get(name) catch .{ .err = "secrets: out of memory" };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// A directed read: only the named store is asked.
|
|
207
|
+
pub fn getfrom(self: *SecretsFeature, store: []const u8, name: []const u8) sekreto.Answer([]const u8) {
|
|
208
|
+
const s = self.state.sek orelse return .{ .err = "secrets: no provider chain" };
|
|
209
|
+
return s.getfrom(store, name) catch .{ .err = "secrets: out of memory" };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Every value the chain has ever resolved, replaced in `text`.
|
|
213
|
+
pub fn redact(self: *SecretsFeature, text: []const u8) []const u8 {
|
|
214
|
+
const s = self.state.sek orelse return text;
|
|
215
|
+
return sekreto.redact(h.A(), text, s.seen.items) catch text;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Resolve now, rather than at the next request. Returns the failure
|
|
219
|
+
/// message when the chain refuses.
|
|
220
|
+
pub fn resolve_now(self: *SecretsFeature) ?[]const u8 {
|
|
221
|
+
const ctx = self.state.rootctx orelse return null;
|
|
222
|
+
resolve(self.state, ctx) catch {
|
|
223
|
+
const e = ctx.take_err();
|
|
224
|
+
return if (e) |pe| pe.msg else "secrets: resolution failed";
|
|
225
|
+
};
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fn vname(p: *anyopaque) []const u8 {
|
|
230
|
+
return self_of(p).name;
|
|
231
|
+
}
|
|
232
|
+
fn vactive(p: *anyopaque) bool {
|
|
233
|
+
return self_of(p).active;
|
|
234
|
+
}
|
|
235
|
+
fn vaddopts(p: *anyopaque) Value {
|
|
236
|
+
return self_of(p).add_opts;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Sync by feature contract: build the chain, never look anything up
|
|
240
|
+
// here.
|
|
241
|
+
fn vinit(p: *anyopaque, ctx: *Context, options: Value) void {
|
|
242
|
+
const self = self_of(p);
|
|
243
|
+
self.options = options;
|
|
244
|
+
self.active = sup.fopt_bool(options, "active", false);
|
|
245
|
+
if (!self.active) return;
|
|
246
|
+
|
|
247
|
+
const st = self.state;
|
|
248
|
+
st.rootctx = ctx;
|
|
249
|
+
st.liveopts = ctx.options;
|
|
250
|
+
|
|
251
|
+
// Wrap the transport FIRST. The fail-closed gate needs the seam
|
|
252
|
+
// whenever the feature is active - hooks cannot fail an operation
|
|
253
|
+
// and the raw paths run none - and the exchange additionally needs
|
|
254
|
+
// to SEE responses, since expiry is only ever discovered from one.
|
|
255
|
+
// Installed before the chain is built so that nothing below can
|
|
256
|
+
// leave an active feature with an unguarded transport.
|
|
257
|
+
const util = ctx.util();
|
|
258
|
+
const w = h.A().create(WrapCtx) catch unreachable;
|
|
259
|
+
w.* = .{ .inner = util.fetcher, .state = st };
|
|
260
|
+
util.fetcher = .{ .ctx = @ptrCast(w), .call = wrapCall };
|
|
261
|
+
|
|
262
|
+
st.secretname = sup.fopt_str(options, "name", "apikey");
|
|
263
|
+
st.cache = sup.fopt_bool(options, "cache", true);
|
|
264
|
+
|
|
265
|
+
const xopts = sup.fopt_map(options, "exchange");
|
|
266
|
+
if (sup.fopt_bool(xopts, "active", false)) {
|
|
267
|
+
var statuses: std.ArrayList(i64) = .empty;
|
|
268
|
+
const raw = sup.fopt_list(xopts, "statuses");
|
|
269
|
+
if (raw == .array) {
|
|
270
|
+
for (raw.array.data.items) |v| statuses.append(h.A(), h.to_int(v)) catch {};
|
|
271
|
+
}
|
|
272
|
+
if (0 == statuses.items.len) statuses.append(h.A(), 401) catch {};
|
|
273
|
+
st.exchange = .{
|
|
274
|
+
.path = sup.fopt_str(xopts, "path", "auth/token"),
|
|
275
|
+
.method = sup.fopt_str(xopts, "method", "POST"),
|
|
276
|
+
.request = sup.fopt_str(xopts, "request", "refresh_token"),
|
|
277
|
+
.response = sup.fopt_str(xopts, "response", "access_token"),
|
|
278
|
+
.statuses = statuses.items,
|
|
279
|
+
.retries = sup.fopt_int(xopts, "retries", 1),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const alloc = h.A();
|
|
284
|
+
|
|
285
|
+
// The explicit credential, when set, is the first store in the
|
|
286
|
+
// chain.
|
|
287
|
+
//
|
|
288
|
+
// WHICH option that is depends on the exchange. Without one, the
|
|
289
|
+
// secret being resolved IS the credential the transport sends, so
|
|
290
|
+
// `apikey` is it. With one, the secret is a REFRESH token and
|
|
291
|
+
// `apikey` means the opposite thing - an access token the caller
|
|
292
|
+
// already holds - so the explicit seat belongs to
|
|
293
|
+
// `exchange.refresh`, and apikey is left alone to serve as the
|
|
294
|
+
// starting access token (see resolve_once).
|
|
295
|
+
const explicit: []const u8 = if (st.exchange == null)
|
|
296
|
+
(h.get_str(st.liveopts, "apikey") orelse "")
|
|
297
|
+
else
|
|
298
|
+
sup.fopt_str(xopts, "refresh", "");
|
|
299
|
+
|
|
300
|
+
var specs: std.ArrayList(sekreto.ProviderSpec) = .empty;
|
|
301
|
+
|
|
302
|
+
if (0 != explicit.len) {
|
|
303
|
+
const keyed = sekreto.envkey(alloc, st.secretname, "") catch
|
|
304
|
+
sekreto.Answer([]const u8){ .err = "secrets: out of memory" };
|
|
305
|
+
switch (keyed) {
|
|
306
|
+
.ok => |key| {
|
|
307
|
+
const values = alloc.alloc(sekreto.KeyValue, 1) catch unreachable;
|
|
308
|
+
values[0] = .{ .key = key, .value = explicit };
|
|
309
|
+
specs.append(alloc, .{ .kind = "memory", .name = "options", .values = values }) catch {};
|
|
310
|
+
},
|
|
311
|
+
.err => |message| st.initerr = message,
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const providers = sup.fopt_list(options, "providers");
|
|
316
|
+
if (providers == .array) {
|
|
317
|
+
for (providers.array.data.items) |entry| {
|
|
318
|
+
switch (entry) {
|
|
319
|
+
// A provider written as a callable (see FuncProvider).
|
|
320
|
+
.function => {
|
|
321
|
+
const index = func_providers.items.len;
|
|
322
|
+
func_providers.append(alloc, entry) catch unreachable;
|
|
323
|
+
specs.append(alloc, .{
|
|
324
|
+
.kind = FUNC_KIND,
|
|
325
|
+
.name = "custom",
|
|
326
|
+
.path = std.fmt.allocPrint(alloc, "{d}", .{index}) catch "0",
|
|
327
|
+
}) catch {};
|
|
328
|
+
},
|
|
329
|
+
// A spec map: the same keys every port's spec uses,
|
|
330
|
+
// read back through sekreto's own decoder.
|
|
331
|
+
.object => specs.append(alloc, sekreto.provider.specof(to_plugin_value(entry))) catch {},
|
|
332
|
+
// FAIL CLOSED, never drop. An entry that is neither a
|
|
333
|
+
// callable nor a spec (a bare "hashicorp" where a spec
|
|
334
|
+
// map was meant) must not leave the chain quietly
|
|
335
|
+
// shorter than the options say: sekreto's own wording
|
|
336
|
+
// lands in the init-failure gate, which refuses to send.
|
|
337
|
+
// The zig ProviderSpec is a typed struct, so the refusal
|
|
338
|
+
// is raised at this arm, as the go and rust ports do.
|
|
339
|
+
else => {
|
|
340
|
+
if (st.initerr == null) {
|
|
341
|
+
st.initerr = std.fmt.allocPrint(
|
|
342
|
+
alloc,
|
|
343
|
+
"sekreto: not a provider or a provider spec: {s}",
|
|
344
|
+
.{h.stringify(entry)},
|
|
345
|
+
) catch "sekreto: not a provider or a provider spec";
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (st.initerr != null) return;
|
|
353
|
+
|
|
354
|
+
// The plugin DEFINITIONS the model selected, from the GENERATED
|
|
355
|
+
// module root (feature/secrets/plugins.zig, Config_zig
|
|
356
|
+
// FeaturePlugins). Upstream sekreto's contract since the registry
|
|
357
|
+
// was retired: a kind not passed in is unknown to this Sekreto, so
|
|
358
|
+
// the model's choice of plugin groups IS the SDK's provider
|
|
359
|
+
// vocabulary. Plus this feature's own callable kind.
|
|
360
|
+
var defs: std.ArrayList(sekreto.Definition) = .empty;
|
|
361
|
+
for (sekplugins.SELECTED) |d| defs.append(alloc, d) catch {};
|
|
362
|
+
defs.append(alloc, FUNC_DEFINITION) catch {};
|
|
363
|
+
|
|
364
|
+
st.envmap = environ_map(alloc);
|
|
365
|
+
|
|
366
|
+
const built = sekreto.Sekreto.init(alloc, .{ .io = h.IO(), .env = &st.envmap }, .{
|
|
367
|
+
.providers = specs.items,
|
|
368
|
+
.plugins = defs.items,
|
|
369
|
+
.cache = st.cache,
|
|
370
|
+
}) catch sekreto.Answer(*sekreto.Sekreto){ .err = "secrets: out of memory" };
|
|
371
|
+
|
|
372
|
+
switch (built) {
|
|
373
|
+
.ok => |made| st.sek = made,
|
|
374
|
+
// Init cannot fail construction the way ts's throwing init does;
|
|
375
|
+
// the transport gate refuses to send instead.
|
|
376
|
+
.err => |message| st.initerr = message,
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
fn vdispatch(p: *anyopaque, name: []const u8, ctx: *Context) void {
|
|
381
|
+
// The model declares a PreSpec hook (as go's does); resolution
|
|
382
|
+
// happens at the transport instead, for the reasons in the header.
|
|
383
|
+
_ = p;
|
|
384
|
+
_ = name;
|
|
385
|
+
_ = ctx;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn self_of(p: *anyopaque) *SecretsFeature {
|
|
389
|
+
return @ptrCast(@alignCast(p));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const vtable = Feature.VTable{
|
|
393
|
+
.name = vname,
|
|
394
|
+
.active = vactive,
|
|
395
|
+
.add_options = vaddopts,
|
|
396
|
+
.init = vinit,
|
|
397
|
+
.dispatch = vdispatch,
|
|
398
|
+
};
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// ---- the environment -----------------------------------------------------
|
|
402
|
+
|
|
403
|
+
/// An application-supplied environment map (SecretsFeature.set_environ).
|
|
404
|
+
var given_environ: ?*const Environ.Map = null;
|
|
405
|
+
|
|
406
|
+
/// The map the chain's `env` provider reads: the application's when it gave
|
|
407
|
+
/// one, else the process environment as std's startup code recorded it on
|
|
408
|
+
/// the global single-threaded Io (which core/mem.zig uses for everything
|
|
409
|
+
/// else). Empty - never a crash - on a target where std records nothing.
|
|
410
|
+
fn environ_map(alloc: Allocator) Environ.Map {
|
|
411
|
+
if (given_environ) |given| {
|
|
412
|
+
return given.clone(alloc) catch Environ.Map.init(alloc);
|
|
413
|
+
}
|
|
414
|
+
const process_environ = mem.threaded().environ.process_environ;
|
|
415
|
+
return Environ.createMap(process_environ, alloc) catch Environ.Map.init(alloc);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ---- a provider written as a callable ------------------------------------
|
|
419
|
+
|
|
420
|
+
/// The kind name of a provider given as a plain callable in `providers`.
|
|
421
|
+
const FUNC_KIND = "sdkfunc";
|
|
422
|
+
|
|
423
|
+
/// The callables handed in through `providers`, in registration order.
|
|
424
|
+
///
|
|
425
|
+
/// MODULE-GLOBAL, for the reason sekreto's own port keeps its `building`
|
|
426
|
+
/// slot global: a definition's `define` is a bare function pointer with no
|
|
427
|
+
/// context, and voxgig/plugin option values are strings and numbers, not
|
|
428
|
+
/// pointers - so the spec carries the callable's INDEX here (in `path`)
|
|
429
|
+
/// and `makefunc` reads it back. Append-only for the life of the process.
|
|
430
|
+
var func_providers: std.ArrayList(Value) = .empty;
|
|
431
|
+
|
|
432
|
+
/// A provider given as a plain callable.
|
|
433
|
+
///
|
|
434
|
+
/// A live provider cannot travel inside a Value - the SDK union is a CLOSED
|
|
435
|
+
/// data union, and widening the vendored struct library to carry SDK
|
|
436
|
+
/// objects would be wrong - and a zig provider kind is a comptime
|
|
437
|
+
/// definition, so go's `providers: [&customProvider{...}]` has no direct
|
|
438
|
+
/// translation. This is the Value-shaped answer (the same one rust gives):
|
|
439
|
+
/// the callable is handed the secret NAME and answers
|
|
440
|
+
///
|
|
441
|
+
/// a string -> a HIT
|
|
442
|
+
/// null / no value -> a MISS, the chain continues
|
|
443
|
+
/// { "__err__": "..." } -> an ERROR, which fails the operation
|
|
444
|
+
///
|
|
445
|
+
/// The miss/error split is the whole point: conflating them turns a broken
|
|
446
|
+
/// vault into a silent unauthenticated request. The `__err__` shape is the
|
|
447
|
+
/// one the Value seams already speak (system.fetch signals a transport
|
|
448
|
+
/// failure the same way).
|
|
449
|
+
const FuncProvider = struct {
|
|
450
|
+
f: Value,
|
|
451
|
+
|
|
452
|
+
pub fn lookup(self: *FuncProvider, alloc: Allocator, name: []const u8) Allocator.Error!sekreto.Found {
|
|
453
|
+
const out = h.call_vfn(self.f, h.vstr(name));
|
|
454
|
+
return switch (out) {
|
|
455
|
+
.string => |s| .{ .ok = try alloc.dupe(u8, s) },
|
|
456
|
+
.object => if (h.get_str(out, "__err__")) |message|
|
|
457
|
+
.{ .err = try alloc.dupe(u8, message) }
|
|
458
|
+
else
|
|
459
|
+
.{ .ok = null },
|
|
460
|
+
else => .{ .ok = null },
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
pub fn describe(_: *FuncProvider, alloc: Allocator) Allocator.Error![]const u8 {
|
|
465
|
+
return alloc.dupe(u8, "custom");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
pub fn deinit(_: *FuncProvider, _: Allocator) void {}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
fn makefunc(alloc: Allocator, config: sekreto.Config, spec: sekreto.ProviderSpec) Allocator.Error!sekreto.Answer(sekreto.Provider) {
|
|
472
|
+
_ = config;
|
|
473
|
+
const index = std.fmt.parseInt(usize, spec.path, 10) catch func_providers.items.len;
|
|
474
|
+
if (index >= func_providers.items.len) {
|
|
475
|
+
return .{ .err = try sekreto.fail(alloc, "secrets: no callable provider at {s}", .{spec.path}) };
|
|
476
|
+
}
|
|
477
|
+
return .{ .ok = try sekreto.provide(alloc, FuncProvider, .{ .f = func_providers.items[index] }) };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const FUNC_DEFINITION = sekreto.providerplugin(FUNC_KIND, makefunc);
|
|
481
|
+
|
|
482
|
+
// ---- SDK Value -> voxgig/plugin Value ------------------------------------
|
|
483
|
+
|
|
484
|
+
/// SDK Value -> voxgig/plugin Value, so an option map can be handed to
|
|
485
|
+
/// sekreto's `specof`. The two are different types on purpose: the plugin
|
|
486
|
+
/// value is an arena-allocated pointer tree the host owns for the life of
|
|
487
|
+
/// the process; the SDK's is the vendored struct library's reference-stable
|
|
488
|
+
/// union. Callables have no counterpart and become null.
|
|
489
|
+
fn to_plugin_value(v: Value) *pv.Value {
|
|
490
|
+
return switch (v) {
|
|
491
|
+
.bool => |b| pv.vbool(b),
|
|
492
|
+
.integer => |n| pv.vnum(@floatFromInt(n)),
|
|
493
|
+
.float => |f| pv.vnum(f),
|
|
494
|
+
.string => |s| pv.vstr(pv.dupe(s)),
|
|
495
|
+
.number_string => |s| pv.vstr(pv.dupe(s)),
|
|
496
|
+
.array => |list| blk: {
|
|
497
|
+
const out = pv.vlist();
|
|
498
|
+
for (list.data.items) |item| pv.push(out, to_plugin_value(item));
|
|
499
|
+
break :blk out;
|
|
500
|
+
},
|
|
501
|
+
.object => |m| blk: {
|
|
502
|
+
const out = pv.vmap();
|
|
503
|
+
var it = m.iterator();
|
|
504
|
+
while (it.next()) |kv| pv.set(out, kv.key_ptr.*, to_plugin_value(kv.value_ptr.*));
|
|
505
|
+
break :blk out;
|
|
506
|
+
},
|
|
507
|
+
else => pv.vnull(),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ---- the transport seam --------------------------------------------------
|
|
512
|
+
|
|
513
|
+
const WrapCtx = struct {
|
|
514
|
+
inner: Fetcher,
|
|
515
|
+
state: *State,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
fn wrapCall(p: *anyopaque, ctx: *Context, url: []const u8, fetchdef: Value) E!Value {
|
|
519
|
+
const w: *WrapCtx = @ptrCast(@alignCast(p));
|
|
520
|
+
return transport(w.state, ctx, url, fetchdef, w.inner);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
fn transport(st: *State, ctx: *Context, url: []const u8, fetchdef: Value, inner: Fetcher) E!Value {
|
|
524
|
+
// Fail-closed, at the ONE seam every wire path crosses. Entity ops,
|
|
525
|
+
// direct(), graphql() and the exchange retries all come through here,
|
|
526
|
+
// so resolving HERE is what gives the raw paths - which run no feature
|
|
527
|
+
// hooks at all - the same credential the entity pipeline gets. A
|
|
528
|
+
// provider ERROR refuses the request with the provider's own message;
|
|
529
|
+
// never an unauthenticated send.
|
|
530
|
+
if (st.initerr) |message| return ctx.fail(ERRCODE, message);
|
|
531
|
+
|
|
532
|
+
try resolve(st, ctx);
|
|
533
|
+
|
|
534
|
+
// Inject the resolved credential into THIS request's header. The
|
|
535
|
+
// header was built by prepare_auth from the options apikey; the
|
|
536
|
+
// chain-resolved value lives in feature state instead, so the wrapper
|
|
537
|
+
// writes it here - same construction, same suppression rules - and the
|
|
538
|
+
// shared options map stays untouched. A MISS leaves whatever
|
|
539
|
+
// prepare_auth built, which is nothing.
|
|
540
|
+
if (0 != st.cred.len) reauth(st, fetchdef, st.cred);
|
|
541
|
+
|
|
542
|
+
if (st.exchange == null) return inner.invoke(ctx, url, fetchdef);
|
|
543
|
+
|
|
544
|
+
return with_refresh(st, ctx, url, fetchdef, inner);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// One resolution, shared by every request. A settled SUCCESS is kept only
|
|
548
|
+
// when caching is on; a FAILURE is never kept, so a transient vault outage
|
|
549
|
+
// never poisons the client permanently - the next operation asks again.
|
|
550
|
+
fn resolve(st: *State, ctx: *Context) E!void {
|
|
551
|
+
if (st.initerr) |message| return ctx.fail(ERRCODE, message);
|
|
552
|
+
if (st.resolved) return;
|
|
553
|
+
|
|
554
|
+
resolve_once(st, ctx) catch |e| {
|
|
555
|
+
st.resolved = false;
|
|
556
|
+
return e;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
if (st.cache) st.resolved = true;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
fn resolve_once(st: *State, ctx: *Context) E!void {
|
|
563
|
+
const s = st.sek orelse return;
|
|
564
|
+
|
|
565
|
+
const found = s.trysecret(st.secretname) catch
|
|
566
|
+
return ctx.fail(ERRCODE, "secrets: out of memory");
|
|
567
|
+
|
|
568
|
+
const value: ?[]const u8 = switch (found) {
|
|
569
|
+
// A provider ERROR fails the op (via the transport gate); only a
|
|
570
|
+
// MISS falls through. The message is copied out: sekreto owns and
|
|
571
|
+
// replaces it on the next failure.
|
|
572
|
+
.err => |message| return ctx.fail(ERRCODE, h.A().dupe(u8, message) catch message),
|
|
573
|
+
.ok => |v| v,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
if (st.exchange == null) {
|
|
577
|
+
// An UNCACHED miss after an earlier hit is a revocation: the chain
|
|
578
|
+
// now says no provider has the secret, so the resolved value must
|
|
579
|
+
// not keep going out on the wire. (An explicit apikey OPTION is
|
|
580
|
+
// never lost here - it seats FIRST in the chain as a memory
|
|
581
|
+
// provider, so the chain HITS while one is set and the miss branch
|
|
582
|
+
// is unreachable.)
|
|
583
|
+
st.cred = value orelse "";
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Exchanging: what the chain resolved is the REFRESH token, kept for
|
|
588
|
+
// every later purchase. A miss is not fatal here - an explicit `apikey`
|
|
589
|
+
// may already hold a usable access token, and the API is what gets to
|
|
590
|
+
// say whether it does.
|
|
591
|
+
st.refresh = value orelse "";
|
|
592
|
+
|
|
593
|
+
if (0 == st.cred.len) {
|
|
594
|
+
// A starting access token supplied as the OPTION: read from the
|
|
595
|
+
// frozen options map (no feature ever writes it).
|
|
596
|
+
st.cred = h.get_str(st.liveopts, "apikey") orelse "";
|
|
597
|
+
}
|
|
598
|
+
if (0 != st.cred.len) {
|
|
599
|
+
// A starting access token was supplied. Spend it: if it is stale
|
|
600
|
+
// the API answers with an expiry status and the wrapper buys
|
|
601
|
+
// another, which is the same path expiry takes anyway.
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
_ = try buy(st, ctx);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Buy a token and try the request again when the API says the current one
|
|
609
|
+
// is spent.
|
|
610
|
+
//
|
|
611
|
+
// The retry rewrites the authorization header IN PLACE on the fetchdef,
|
|
612
|
+
// because the header was built by the synchronous prepare_auth before this
|
|
613
|
+
// request left and carries the token that just failed. Rebuilt the way
|
|
614
|
+
// prepare_auth builds it, from the same options.auth.prefix, so the two
|
|
615
|
+
// cannot drift.
|
|
616
|
+
fn with_refresh(st: *State, ctx: *Context, url: []const u8, fetchdef: Value, inner: Fetcher) E!Value {
|
|
617
|
+
// `auth: null` is the documented way to send NO credential, and
|
|
618
|
+
// prepare_auth honours it by removing the header. A refusal of a
|
|
619
|
+
// deliberately unauthenticated request is not an expired token and
|
|
620
|
+
// cannot be fixed by buying one - retrying would transmit exactly the
|
|
621
|
+
// credential the caller suppressed.
|
|
622
|
+
if (!auth_active(st.liveopts)) return inner.invoke(ctx, url, fetchdef);
|
|
623
|
+
|
|
624
|
+
const max = if (st.exchange) |x| x.retries else 0;
|
|
625
|
+
var attempt: i64 = 0;
|
|
626
|
+
|
|
627
|
+
while (true) {
|
|
628
|
+
// The credential THIS attempt goes out with, captured before it
|
|
629
|
+
// leaves: it is what tells a stale refusal apart from a fresh one.
|
|
630
|
+
const used = st.cred;
|
|
631
|
+
|
|
632
|
+
const res = try inner.invoke(ctx, url, fetchdef);
|
|
633
|
+
|
|
634
|
+
if (attempt >= max or !spent(st, res)) return res;
|
|
635
|
+
|
|
636
|
+
// A token another request already bought is spent before buying: a
|
|
637
|
+
// second exchange for a token that is already fresh is wasted, and
|
|
638
|
+
// on a provider that invalidates the previous credential on
|
|
639
|
+
// issuance it breaks the first request's own retry.
|
|
640
|
+
const current = st.cred;
|
|
641
|
+
const token: []const u8 = if (0 != current.len and !std.mem.eql(u8, current, used))
|
|
642
|
+
current
|
|
643
|
+
else
|
|
644
|
+
buy(st, ctx) catch {
|
|
645
|
+
// The purchase failed: answer with the API's own refusal
|
|
646
|
+
// rather than this one. The caller asked for data, and the
|
|
647
|
+
// refusal is the more useful of the two - the exchange error
|
|
648
|
+
// is a symptom. The parked error must not outlive this
|
|
649
|
+
// decision, or a SUCCESSFUL return would carry a pending
|
|
650
|
+
// failure into the next pipeline stage.
|
|
651
|
+
_ = ctx.take_err();
|
|
652
|
+
return res;
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
reauth(st, fetchdef, token);
|
|
656
|
+
attempt += 1;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
fn spent(st: *State, res: Value) bool {
|
|
661
|
+
const status = sup.fres_status(res) orelse return false;
|
|
662
|
+
const x = st.exchange orelse return false;
|
|
663
|
+
for (x.statuses) |s| {
|
|
664
|
+
if (s == status) return true;
|
|
665
|
+
}
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Is auth ACTIVE? Read the raw map rather than through getp, which applies
|
|
670
|
+
// the Group A rule and reads a stored null as "no value" - so it cannot tell
|
|
671
|
+
// an absent auth from a suppressed one, and only the latter is a
|
|
672
|
+
// suppression. Same reading make_options uses to preserve `auth: null`.
|
|
673
|
+
fn auth_active(options: Value) bool {
|
|
674
|
+
return switch (options) {
|
|
675
|
+
.object => |m| if (m.get("auth")) |a| a == .object else false,
|
|
676
|
+
else => false,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
fn reauth(st: *State, fetchdef: Value, token: []const u8) void {
|
|
681
|
+
const headers = h.getp(fetchdef, "headers");
|
|
682
|
+
if (headers != .object) return;
|
|
683
|
+
|
|
684
|
+
// Suppressed auth means NO header, the same answer prepare_auth gives.
|
|
685
|
+
// Reached defensively - with_refresh does not retry at all when auth is
|
|
686
|
+
// suppressed - but this is the function that writes the credential, so
|
|
687
|
+
// it is where the rule has to hold.
|
|
688
|
+
if (!auth_active(st.liveopts)) {
|
|
689
|
+
h.del_prop(headers, h.vstr(HEADER_AUTH));
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const prefix: []const u8 = switch (h.getpath(&.{ "auth", "prefix" }, st.liveopts)) {
|
|
694
|
+
.string => |s| s,
|
|
695
|
+
else => "",
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// Empty prefix (a raw apiKey credential) must not add a leading space.
|
|
699
|
+
if (0 == prefix.len) {
|
|
700
|
+
h.setp(headers, HEADER_AUTH, h.vstr(token));
|
|
701
|
+
} else {
|
|
702
|
+
h.setp(headers, HEADER_AUTH, h.vstr(std.fmt.allocPrint(h.A(), "{s} {s}", .{ prefix, token }) catch token));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ---- the exchange --------------------------------------------------------
|
|
707
|
+
|
|
708
|
+
fn is_live(st: *State) bool {
|
|
709
|
+
const ctx = st.rootctx orelse return true;
|
|
710
|
+
const client = ctx.client orelse return true;
|
|
711
|
+
return std.mem.eql(u8, client.mode, "live");
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Buy an access token with the refresh token. The slot is a plain field
|
|
715
|
+
// (single-threaded SDK): the next expiry buys a fresh token rather than
|
|
716
|
+
// replaying this result.
|
|
717
|
+
fn buy(st: *State, ctx: *Context) E![]const u8 {
|
|
718
|
+
const x = st.exchange orelse return ctx.fail(ERRCODE, "secrets: no exchange configured");
|
|
719
|
+
|
|
720
|
+
// TEST MODE BUYS NOTHING. The test feature replaces the transport so
|
|
721
|
+
// no request leaves the process; an exchange here would be the one
|
|
722
|
+
// HTTP call it could not stop, and it would need a live token endpoint
|
|
723
|
+
// for a suite whose whole point is not needing one. A deterministic,
|
|
724
|
+
// obviously-fake token instead - the same answer make_options gives a
|
|
725
|
+
// required server variable, for the same reason.
|
|
726
|
+
if (!is_live(st)) {
|
|
727
|
+
const token = std.fmt.allocPrint(h.A(), "test-{s}", .{x.response}) catch "test-token";
|
|
728
|
+
st.cred = token;
|
|
729
|
+
return token;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (0 == st.refresh.len) {
|
|
733
|
+
return ctx.fail(ERRCODE, std.fmt.allocPrint(
|
|
734
|
+
h.A(),
|
|
735
|
+
"secrets: no refresh token: the provider chain has no '{s}', and feature.secrets.exchange.refresh is unset",
|
|
736
|
+
.{st.secretname},
|
|
737
|
+
) catch "secrets: no refresh token");
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const options: Value = if (ctx.client) |client| client.options_map() else st.liveopts;
|
|
741
|
+
|
|
742
|
+
// The token endpoint is RELATIVE to the base, which already carries
|
|
743
|
+
// whatever account or tenant segment the server URL declares.
|
|
744
|
+
const base = std.mem.trimEnd(u8, h.get_str(options, "base") orelse "", "/");
|
|
745
|
+
const path = std.mem.trimStart(u8, x.path, "/");
|
|
746
|
+
const url = std.fmt.allocPrint(h.A(), "{s}/{s}", .{ base, path }) catch x.path;
|
|
747
|
+
|
|
748
|
+
// The body is SERIALISED, never concatenated: a refresh token (or a
|
|
749
|
+
// configured request-field name) carrying a quote, backslash or newline
|
|
750
|
+
// must arrive as that literal value, not as malformed JSON.
|
|
751
|
+
const body = h.jsonify_compact(h.jo(&.{.{ x.request, h.vstr(st.refresh) }}));
|
|
752
|
+
|
|
753
|
+
const sysfetch = h.getpath(&.{ "system", "fetch" }, options);
|
|
754
|
+
|
|
755
|
+
var res: Value = undefined;
|
|
756
|
+
if (sysfetch == .function) {
|
|
757
|
+
// The caller's transport, when there is one - the same seam the SDK
|
|
758
|
+
// itself uses for every request.
|
|
759
|
+
res = h.call_vfn(sysfetch, h.ja(&.{
|
|
760
|
+
h.vstr(url),
|
|
761
|
+
h.jo(&.{
|
|
762
|
+
.{ "method", h.vstr(x.method) },
|
|
763
|
+
.{ "headers", h.jo(&.{.{ "content-type", h.vstr("application/json") }}) },
|
|
764
|
+
.{ "body", h.vstr(body) },
|
|
765
|
+
}),
|
|
766
|
+
}));
|
|
767
|
+
if (h.get_str(res, "__err__")) |message| return ctx.fail(ERRCODE, message);
|
|
768
|
+
} else {
|
|
769
|
+
// No custom transport supplied - the ordinary case (make_options
|
|
770
|
+
// leaves system.fetch unset, and the SDK's default transport has no
|
|
771
|
+
// HTTP client). See raw_exchange_fetch.
|
|
772
|
+
res = try raw_exchange_fetch(ctx, url, x.method, body);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const status = h.to_int(h.getp(res, "status"));
|
|
776
|
+
if (200 > status or 300 <= status) {
|
|
777
|
+
return ctx.fail(ERRCODE, std.fmt.allocPrint(
|
|
778
|
+
h.A(),
|
|
779
|
+
"secrets: token exchange failed: {d} from {s}",
|
|
780
|
+
.{ status, url },
|
|
781
|
+
) catch "secrets: token exchange failed");
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const jf = h.getp(res, "json");
|
|
785
|
+
const payload: Value = if (jf == .function) h.call_json(jf) else h.getp(res, "body");
|
|
786
|
+
|
|
787
|
+
const token = h.get_str(payload, x.response) orelse "";
|
|
788
|
+
if (0 == token.len) {
|
|
789
|
+
return ctx.fail(ERRCODE, std.fmt.allocPrint(
|
|
790
|
+
h.A(),
|
|
791
|
+
"secrets: token exchange returned no '{s}' field from {s}",
|
|
792
|
+
.{ x.response, url },
|
|
793
|
+
) catch "secrets: token exchange returned no token");
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
st.cred = token;
|
|
797
|
+
return token;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// The token-exchange transport of last resort: the vendored sekreto
|
|
801
|
+
// plugins' own HTTP round-trip (std.http.Client + std TLS, always exported
|
|
802
|
+
// by the generated feature/secrets/plugins.zig), answering in the shape the
|
|
803
|
+
// system.fetch seam promises ("status" + "json"). It exists so an exchange
|
|
804
|
+
// works with ordinary SDK options - requiring a custom transport for the
|
|
805
|
+
// COMMON case would reject every live token purchase before a request was
|
|
806
|
+
// made. It does NOT go through sekreto's checkaddr: a plaintext loopback
|
|
807
|
+
// token endpoint is the caller's business, exactly as it is for go's
|
|
808
|
+
// net/http path.
|
|
809
|
+
fn raw_exchange_fetch(ctx: *Context, url: []const u8, method_name: []const u8, body: []const u8) E!Value {
|
|
810
|
+
const method = std.meta.stringToEnum(std.http.Method, method_name) orelse .POST;
|
|
811
|
+
const headers = [_]std.http.Header{
|
|
812
|
+
.{ .name = "content-type", .value = "application/json" },
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
const result = sekplugins.httpjson.fetchjson(h.A(), h.IO(), method, url, &headers, body) catch
|
|
816
|
+
return ctx.fail(ERRCODE, "secrets: out of memory");
|
|
817
|
+
|
|
818
|
+
return switch (result) {
|
|
819
|
+
// sekreto's own message ("sekreto: cannot reach <url>: <reason>").
|
|
820
|
+
.err => |message| ctx.fail(ERRCODE, message),
|
|
821
|
+
.ok => |response| h.jo(&.{
|
|
822
|
+
.{ "status", h.vnum(@intCast(response.status)) },
|
|
823
|
+
.{ "statusText", h.vstr("") },
|
|
824
|
+
.{ "headers", h.omap() },
|
|
825
|
+
.{ "json", h.json_thunk(from_std_json(response.body)) },
|
|
826
|
+
}),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// std.json.Value (what fetchjson parses) -> the SDK Value.
|
|
831
|
+
fn from_std_json(v: ?std.json.Value) Value {
|
|
832
|
+
const value = v orelse return h.vnull();
|
|
833
|
+
return switch (value) {
|
|
834
|
+
.null => h.vnull(),
|
|
835
|
+
.bool => |b| h.vbool(b),
|
|
836
|
+
.integer => |n| h.vnum(n),
|
|
837
|
+
.float => |f| h.vfloat(f),
|
|
838
|
+
.number_string => |s| h.vstr(h.A().dupe(u8, s) catch s),
|
|
839
|
+
.string => |s| h.vstr(h.A().dupe(u8, s) catch s),
|
|
840
|
+
.array => |items| blk: {
|
|
841
|
+
const out = h.olist();
|
|
842
|
+
for (items.items) |item| out.array.append(from_std_json(item)) catch {};
|
|
843
|
+
break :blk out;
|
|
844
|
+
},
|
|
845
|
+
.object => |m| blk: {
|
|
846
|
+
const out = h.omap();
|
|
847
|
+
var it = m.iterator();
|
|
848
|
+
while (it.next()) |kv| {
|
|
849
|
+
h.setp(out, h.A().dupe(u8, kv.key_ptr.*) catch kv.key_ptr.*, from_std_json(kv.value_ptr.*));
|
|
850
|
+
}
|
|
851
|
+
break :blk out;
|
|
852
|
+
},
|
|
853
|
+
};
|
|
854
|
+
}
|