@synopackageland/cli 0.3.10 → 0.4.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.
Files changed (280) hide show
  1. package/README.md +20 -9
  2. package/dist/bin.js +1 -2
  3. package/dist/build-spk/generator.d.ts +0 -3
  4. package/dist/build-spk/generator.js +135 -287
  5. package/dist/build-spk/native-lifecycle.js +0 -3
  6. package/dist/build-spk/native-payload.js +0 -1
  7. package/dist/cli.js +171 -151
  8. package/dist/compose/analyze.d.ts +1 -2
  9. package/dist/compose/analyze.js +5 -48
  10. package/dist/contract/unsupported-types.d.ts +10 -0
  11. package/dist/contract/unsupported-types.js +49 -0
  12. package/dist/contract/validate.js +25 -2
  13. package/dist/contract/web-launch.js +0 -2
  14. package/dist/deploy/deploy.d.ts +1 -4
  15. package/dist/deploy/deploy.js +45 -156
  16. package/dist/deploy/homes-values.js +1 -1
  17. package/dist/deploy/plan.d.ts +18 -0
  18. package/dist/deploy/plan.js +54 -0
  19. package/dist/deploy/spk-version.d.ts +2 -14
  20. package/dist/deploy/spk-version.js +25 -111
  21. package/dist/dev.d.ts +1 -3
  22. package/dist/dev.js +14 -28
  23. package/dist/error-registry.js +810 -862
  24. package/dist/host/dsm-client.js +12 -272
  25. package/dist/host/dsm-port-profile.d.ts +0 -1
  26. package/dist/host/dsm-port-profile.js +0 -2
  27. package/dist/host/types.d.ts +1 -83
  28. package/dist/index.d.ts +0 -9
  29. package/dist/index.js +0 -6
  30. package/dist/info.d.ts +0 -1
  31. package/dist/info.js +3 -10
  32. package/dist/init.d.ts +11 -1
  33. package/dist/init.js +112 -4
  34. package/dist/log.js +2 -3
  35. package/dist/native/validate.js +0 -1
  36. package/dist/standalone/binary.d.ts +9 -0
  37. package/dist/standalone/binary.js +67 -0
  38. package/dist/standalone/build.d.ts +25 -0
  39. package/dist/standalone/build.js +108 -0
  40. package/dist/standalone/dsm-window.d.ts +26 -0
  41. package/dist/standalone/dsm-window.js +251 -0
  42. package/dist/standalone/helper.d.ts +31 -0
  43. package/dist/standalone/helper.js +104 -0
  44. package/dist/standalone/i18n.d.ts +28 -0
  45. package/dist/standalone/i18n.js +161 -0
  46. package/dist/standalone/inspect-spk.d.ts +21 -0
  47. package/dist/standalone/inspect-spk.js +160 -0
  48. package/dist/standalone/manifest.d.ts +71 -0
  49. package/dist/standalone/manifest.js +276 -0
  50. package/dist/standalone/root-messages.d.ts +7 -0
  51. package/dist/standalone/root-messages.js +250 -0
  52. package/dist/standalone/root-ops.d.ts +42 -0
  53. package/dist/standalone/root-ops.js +101 -0
  54. package/dist/standalone/root-window.d.ts +35 -0
  55. package/dist/standalone/root-window.js +290 -0
  56. package/dist/standalone/spk.d.ts +22 -0
  57. package/dist/standalone/spk.js +205 -0
  58. package/dist/templates/binary/synology-app.yaml +7 -0
  59. package/dist/templates/hello-files/compose.yaml +0 -1
  60. package/dist/templates/hello-web/compose.yaml +13 -10
  61. package/dist/templates/hello-web/html/app.js +1 -23
  62. package/dist/templates/hello-web/html/index.html +1 -3
  63. package/dist/templates/hello-web-page/compose.yaml +13 -10
  64. package/dist/templates/hello-web-page/html/app.js +1 -23
  65. package/dist/templates/hello-web-page/html/index.html +1 -3
  66. package/dist/templates/native-go/README.md +268 -0
  67. package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
  68. package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
  69. package/dist/templates/native-go/backend/go.mod +3 -0
  70. package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
  71. package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
  72. package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
  73. package/dist/templates/native-go/backend/internal/api/server.go +155 -0
  74. package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
  75. package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
  76. package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
  77. package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
  78. package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  79. package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
  80. package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
  81. package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  82. package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  83. package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  84. package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
  85. package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  86. package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
  87. package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
  88. package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
  89. package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  90. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  91. package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  92. package/dist/templates/native-go/dsm-package.yaml +58 -0
  93. package/dist/templates/native-go/gitignore +1 -0
  94. package/dist/templates/native-go/i18n/glossary.json +662 -0
  95. package/dist/templates/native-go/i18n/messages/de.json +28 -0
  96. package/dist/templates/native-go/i18n/messages/en.json +28 -0
  97. package/dist/templates/native-go/i18n/messages/es.json +28 -0
  98. package/dist/templates/native-go/i18n/messages/fr.json +28 -0
  99. package/dist/templates/native-go/i18n/messages/it.json +28 -0
  100. package/dist/templates/native-go/i18n/messages/ja.json +28 -0
  101. package/dist/templates/native-go/i18n/messages/ko.json +28 -0
  102. package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
  103. package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
  104. package/dist/templates/native-go/ui/app.css +35 -0
  105. package/dist/templates/native-go/ui/app.js +94 -0
  106. package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  107. package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  108. package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
  109. package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
  110. package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
  111. package/dist/templates/native-go-better-auth/node/package.json +1 -0
  112. package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  113. package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  114. package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  115. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  116. package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
  117. package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  118. package/dist/templates/native-go-root/root/whoami.sh +6 -0
  119. package/dist/templates/native-go-vue/README.md +110 -0
  120. package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
  121. package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
  122. package/dist/templates/native-go-vue/frontend/package.json +6 -0
  123. package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
  124. package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
  125. package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
  126. package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
  127. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  128. package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
  129. package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
  130. package/dist/templates/native-go-vue/gitignore +2 -0
  131. package/dist/types.d.ts +1 -1
  132. package/dist/validate.js +11 -7
  133. package/package.json +3 -7
  134. package/schema/synology-app.schema.json +36 -37
  135. package/skills/SKILL.md +4 -48
  136. package/skills/reference/create.md +11 -83
  137. package/skills/reference/deploy.md +7 -78
  138. package/skills/reference/diagnose.md +4 -101
  139. package/skills/reference/files.md +5 -41
  140. package/skills/reference/identity.md +3 -29
  141. package/skills/reference/wrap.md +0 -2
  142. package/templates/binary/synology-app.yaml +7 -0
  143. package/templates/hello-files/compose.yaml +0 -1
  144. package/templates/hello-web/compose.yaml +13 -10
  145. package/templates/hello-web/html/app.js +1 -23
  146. package/templates/hello-web/html/index.html +1 -3
  147. package/templates/hello-web-page/compose.yaml +13 -10
  148. package/templates/hello-web-page/html/app.js +1 -23
  149. package/templates/hello-web-page/html/index.html +1 -3
  150. package/templates/native-go/README.md +268 -0
  151. package/templates/native-go/backend/cmd/app/main.go +49 -0
  152. package/templates/native-go/backend/cmd/helper/main.go +38 -0
  153. package/templates/native-go/backend/go.mod +3 -0
  154. package/templates/native-go/backend/internal/api/errors.go +33 -0
  155. package/templates/native-go/backend/internal/api/registry.go +94 -0
  156. package/templates/native-go/backend/internal/api/registry_test.go +329 -0
  157. package/templates/native-go/backend/internal/api/server.go +155 -0
  158. package/templates/native-go/backend/internal/app/root_none.go +9 -0
  159. package/templates/native-go/backend/internal/app/routes.go +127 -0
  160. package/templates/native-go/backend/internal/app/routes_test.go +167 -0
  161. package/templates/native-go/backend/internal/dsm/auth.go +118 -0
  162. package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  163. package/templates/native-go/backend/internal/helper/control.go +219 -0
  164. package/templates/native-go/backend/internal/helper/control_test.go +242 -0
  165. package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  166. package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  167. package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  168. package/templates/native-go/backend/internal/helper/process_other.go +5 -0
  169. package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  170. package/templates/native-go/backend/internal/helper/status.go +271 -0
  171. package/templates/native-go/backend/internal/helper/status_test.go +205 -0
  172. package/templates/native-go/backend/internal/helper/supervision.go +84 -0
  173. package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  174. package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  175. package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  176. package/templates/native-go/dsm-package.yaml +58 -0
  177. package/templates/native-go/gitignore +1 -0
  178. package/templates/native-go/i18n/glossary.json +662 -0
  179. package/templates/native-go/i18n/messages/de.json +28 -0
  180. package/templates/native-go/i18n/messages/en.json +28 -0
  181. package/templates/native-go/i18n/messages/es.json +28 -0
  182. package/templates/native-go/i18n/messages/fr.json +28 -0
  183. package/templates/native-go/i18n/messages/it.json +28 -0
  184. package/templates/native-go/i18n/messages/ja.json +28 -0
  185. package/templates/native-go/i18n/messages/ko.json +28 -0
  186. package/templates/native-go/i18n/messages/zh-CN.json +28 -0
  187. package/templates/native-go/i18n/messages/zh-TW.json +28 -0
  188. package/templates/native-go/ui/app.css +35 -0
  189. package/templates/native-go/ui/app.js +94 -0
  190. package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  191. package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  192. package/templates/native-go-better-auth/node/app.mjs +72 -0
  193. package/templates/native-go-better-auth/node/build.mjs +2 -0
  194. package/templates/native-go-better-auth/node/package-lock.json +862 -0
  195. package/templates/native-go-better-auth/node/package.json +1 -0
  196. package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  197. package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  198. package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  199. package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  200. package/templates/native-go-root/backend/internal/app/root.go +107 -0
  201. package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  202. package/templates/native-go-root/root/whoami.sh +6 -0
  203. package/templates/native-go-vue/README.md +110 -0
  204. package/templates/native-go-vue/frontend/DESIGN.md +78 -0
  205. package/templates/native-go-vue/frontend/package-lock.json +2507 -0
  206. package/templates/native-go-vue/frontend/package.json +6 -0
  207. package/templates/native-go-vue/frontend/src/App.vue +132 -0
  208. package/templates/native-go-vue/frontend/src/context.ts +17 -0
  209. package/templates/native-go-vue/frontend/src/main.ts +26 -0
  210. package/templates/native-go-vue/frontend/src/style.css +14 -0
  211. package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  212. package/templates/native-go-vue/frontend/tsconfig.json +1 -0
  213. package/templates/native-go-vue/frontend/vite.config.ts +8 -0
  214. package/templates/native-go-vue/gitignore +2 -0
  215. package/dist/attestation/app-package.d.ts +0 -86
  216. package/dist/attestation/app-package.js +0 -206
  217. package/dist/attestation/install-on-host.d.ts +0 -14
  218. package/dist/attestation/install-on-host.js +0 -52
  219. package/dist/attestation/keyring.d.ts +0 -21
  220. package/dist/attestation/keyring.js +0 -168
  221. package/dist/attestation/platform-service.d.ts +0 -70
  222. package/dist/attestation/platform-service.js +0 -250
  223. package/dist/build-spk/broker-glue.d.ts +0 -16
  224. package/dist/build-spk/broker-glue.js +0 -40
  225. package/dist/deploy/catalog-publish.d.ts +0 -3
  226. package/dist/deploy/catalog-publish.js +0 -7
  227. package/dist/doctor.d.ts +0 -22
  228. package/dist/doctor.js +0 -822
  229. package/dist/error-collector.d.ts +0 -2
  230. package/dist/error-collector.js +0 -46
  231. package/dist/host/broker-paths.d.ts +0 -1
  232. package/dist/host/broker-paths.js +0 -1
  233. package/dist/host/broker-upgrade-preflight.d.ts +0 -61
  234. package/dist/host/broker-upgrade-preflight.js +0 -156
  235. package/dist/host/digests.d.ts +0 -5
  236. package/dist/host/digests.js +0 -63
  237. package/dist/host/fake-nas.d.ts +0 -49
  238. package/dist/host/fake-nas.js +0 -193
  239. package/dist/mock-broker/bootstrap.d.ts +0 -2
  240. package/dist/mock-broker/bootstrap.js +0 -8
  241. package/dist/mock-broker/files.d.ts +0 -60
  242. package/dist/mock-broker/files.js +0 -408
  243. package/dist/mock-broker/oidc.d.ts +0 -31
  244. package/dist/mock-broker/oidc.js +0 -161
  245. package/dist/mock-broker/path.d.ts +0 -9
  246. package/dist/mock-broker/path.js +0 -50
  247. package/dist/mock-broker/server.d.ts +0 -19
  248. package/dist/mock-broker/server.js +0 -350
  249. package/dist/publish/build-ledger.d.ts +0 -22
  250. package/dist/publish/build-ledger.js +0 -26
  251. package/dist/publish/community-path.d.ts +0 -6
  252. package/dist/publish/community-path.js +0 -41
  253. package/dist/publish/curated-release.d.ts +0 -78
  254. package/dist/publish/curated-release.js +0 -883
  255. package/dist/publish/file-build-ledger.d.ts +0 -18
  256. package/dist/publish/file-build-ledger.js +0 -268
  257. package/dist/publish/github-pr.d.ts +0 -25
  258. package/dist/publish/github-pr.js +0 -85
  259. package/dist/publish/github-repo.d.ts +0 -8
  260. package/dist/publish/github-repo.js +0 -21
  261. package/dist/publish/platform-service-release.d.ts +0 -60
  262. package/dist/publish/platform-service-release.js +0 -333
  263. package/dist/publish/preflight.d.ts +0 -24
  264. package/dist/publish/preflight.js +0 -138
  265. package/dist/publish/publish.d.ts +0 -27
  266. package/dist/publish/publish.js +0 -136
  267. package/dist/publish/sync-definition.d.ts +0 -4
  268. package/dist/publish/sync-definition.js +0 -48
  269. package/dist/release-bin.d.ts +0 -2
  270. package/dist/release-bin.js +0 -23
  271. package/dist/templates/hello-web/html/sdk.js +0 -68
  272. package/dist/templates/hello-web-page/html/sdk.js +0 -68
  273. package/dist/templates/native/synology-app.yaml +0 -7
  274. package/dist/test-host.d.ts +0 -15
  275. package/dist/test-host.js +0 -37
  276. package/templates/hello-web/html/sdk.js +0 -68
  277. package/templates/hello-web-page/html/sdk.js +0 -68
  278. package/templates/native/synology-app.yaml +0 -7
  279. /package/dist/templates/{native → binary}/native.yaml +0 -0
  280. /package/templates/{native → binary}/native.yaml +0 -0
@@ -0,0 +1,113 @@
1
+ package app
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "dsmapp/internal/api"
7
+ "encoding/json"
8
+ "os"
9
+ "os/exec"
10
+ "path/filepath"
11
+ "strings"
12
+ "testing"
13
+ )
14
+
15
+ func TestIdentityCGIBindsFreshCallerAndPersistsStore(t *testing.T) {
16
+ cfg := personalFixture(t)
17
+ old := identityRun
18
+ t.Cleanup(func() { identityRun = old })
19
+ tickets := map[string]bool{}
20
+ identityRun = func(_ context.Context, cfg Config, input []byte) ([]byte, error) {
21
+ var in struct {
22
+ Ticket string
23
+ Principal map[string]string
24
+ Store map[string]any
25
+ }
26
+ if err := json.Unmarshal(input, &in); err != nil {
27
+ t.Fatal(err)
28
+ }
29
+ if len(in.Ticket) != 64 || tickets[in.Ticket] || in.Principal["name"] != "normal1" || in.Principal["uid"] != "1001" {
30
+ t.Fatal("not fresh native caller", in)
31
+ }
32
+ tickets[in.Ticket] = true
33
+ return []byte(`{"store":{"marker":"saved"},"result":{"ok":true,"user":"normal1","cookie":"session=fixture"}}`), nil
34
+ }
35
+ for i := 0; i < 2; i++ {
36
+ code, out := personalCall(t, cfg, "normal1", "identity.login", `{}`)
37
+ if code != "" || out["data"].(map[string]any)["user"] != "normal1" {
38
+ t.Fatal(code, out)
39
+ }
40
+ }
41
+ if len(tickets) != 2 {
42
+ t.Fatal(tickets)
43
+ }
44
+ data, _ := os.ReadFile(filepath.Join(cfg.DataDir, "identity/store.json"))
45
+ if !strings.Contains(string(data), "saved") {
46
+ t.Fatal(string(data))
47
+ }
48
+ for _, params := range []string{`{"user":"normal2"}`, `{"ticket":"forged"}`, `{"path":"/etc/passwd"}`} {
49
+ if code, _ := personalCall(t, cfg, "normal1", "identity.login", params); code != "bad_request" {
50
+ t.Fatal(code)
51
+ }
52
+ }
53
+ if code, _ := call(t, cfg, stubAuth{err: api.ErrNoSession}, "identity.login", `{}`); code != "session_required" {
54
+ t.Fatal(code)
55
+ }
56
+ }
57
+ func TestIdentityRefusesSymlinkStore(t *testing.T) {
58
+ cfg := personalFixture(t)
59
+ os.Mkdir(filepath.Join(cfg.DataDir, "identity"), 0700)
60
+ outside := filepath.Join(t.TempDir(), "victim")
61
+ os.WriteFile(outside, []byte("unchanged"), 0600)
62
+ os.Symlink(outside, filepath.Join(cfg.DataDir, "identity/store.json"))
63
+ if code, _ := personalCall(t, cfg, "normal1", "identity.login", `{}`); code != "identity_unavailable" {
64
+ t.Fatal(code)
65
+ }
66
+ b, _ := os.ReadFile(outside)
67
+ if string(b) != "unchanged" {
68
+ t.Fatal("escaped")
69
+ }
70
+ }
71
+
72
+ func TestIdentityRealBundledNodeRoundTrip(t *testing.T) {
73
+ bundle := os.Getenv("M1B_NODE_FIXTURE_BUNDLE")
74
+ node := os.Getenv("M1B_NODE_FIXTURE_EXECUTABLE")
75
+ if bundle == "" || node == "" {
76
+ t.Skip("artifact runner provides the selected Node bundle")
77
+ }
78
+ cfg := personalFixture(t)
79
+ old := identityRun
80
+ t.Cleanup(func() { identityRun = old })
81
+ identityRun = func(ctx context.Context, _ Config, input []byte) ([]byte, error) {
82
+ cmd := exec.CommandContext(ctx, node, bundle)
83
+ cmd.Env = []string{"NODE_ENV=production", "PATH=/usr/bin:/bin"}
84
+ cmd.Stdin = bytes.NewReader(input)
85
+ var out limitedIdentityOutput
86
+ cmd.Stdout = &out
87
+ if err := cmd.Run(); err != nil {
88
+ return nil, err
89
+ }
90
+ return out.Bytes(), nil
91
+ }
92
+ code, out := personalCall(t, cfg, "normal1", "identity.login", `{}`)
93
+ if code != "" {
94
+ t.Fatal(code, out)
95
+ }
96
+ cookie := out["data"].(map[string]any)["cookie"].(string)
97
+ if cookie == "" {
98
+ t.Fatal("no app session")
99
+ }
100
+ params, _ := json.Marshal(map[string]string{"cookie": cookie})
101
+ if code, _ := personalCall(t, cfg, "normal1", "identity.current", string(params)); code != "" {
102
+ t.Fatal(code)
103
+ }
104
+ if code, _ := personalCall(t, cfg, "normal2", "identity.current", string(params)); code != "identity_refused" {
105
+ t.Fatal("cross-user session accepted", code)
106
+ }
107
+ if code, _ := personalCall(t, cfg, "normal1", "identity.logout", string(params)); code != "" {
108
+ t.Fatal(code)
109
+ }
110
+ if code, _ := personalCall(t, cfg, "normal1", "identity.current", string(params)); code != "identity_refused" {
111
+ t.Fatal("logout not durable", code)
112
+ }
113
+ }
@@ -0,0 +1,72 @@
1
+ import {betterAuth} from 'better-auth';
2
+ import {createAuthEndpoint, APIError} from 'better-auth/api';
3
+ import {setSessionCookie} from 'better-auth/cookies';
4
+ import {memoryAdapter} from '@better-auth/memory-adapter';
5
+ import {randomBytes, createHash, timingSafeEqual} from 'node:crypto';
6
+ import {fileURLToPath} from 'node:url';
7
+ import {realpathSync} from 'node:fs';
8
+
9
+ // A ticket arrives only through the trusted CGI's stdin, never through a
10
+ // public HTTP listener. One Node process handles one request. Every operation
11
+ // consumes the fresh native ticket; an app session alone carries no NAS rights.
12
+ export function makeApp({packageID,store,ticket,principal}) {
13
+ const origin=`https://${packageID.toLowerCase()}.invalid`;
14
+ const prefix=`spl${createHash('sha256').update(packageID).digest('hex').slice(0,16)}`;
15
+ store.secret ||= randomBytes(32).toString('hex');
16
+ store.db ||= {user:[],session:[],account:[],verification:[]};
17
+ store.bindings ||= {};
18
+ // Hydrate dates after a CGI restart. This JSON snapshot adapter is a bounded
19
+ // example; production DB migration and account lifecycle policy belong to M2.
20
+ for(const rows of Object.values(store.db)) for(const row of rows) for(const key of ['createdAt','updatedAt','expiresAt']) if(row[key]) row[key]=new Date(row[key]);
21
+ let available=true;
22
+ function consume(value){
23
+ if(!available||typeof value!=='string'||!ticket||value.length!==ticket.length||!timingSafeEqual(Buffer.from(value),Buffer.from(ticket))) throw new APIError('UNAUTHORIZED',{message:'native_ticket_required'});
24
+ available=false;
25
+ if(!principal?.name||!/^\d+$/.test(principal.uid)||!/^\d+$/.test(principal.gid)) throw new APIError('UNAUTHORIZED',{message:'native_identity_invalid'});
26
+ return principal;
27
+ }
28
+ const key=p=>JSON.stringify([packageID,p.name,p.uid,p.gid]);
29
+ const auth=betterAuth({baseURL:origin,basePath:'/api/auth',secret:store.secret,database:memoryAdapter(store.db),
30
+ session:{expiresIn:60,updateAge:0,disableSessionRefresh:true,cookieCache:{enabled:false}},
31
+ advanced:{cookiePrefix:prefix,useSecureCookies:true},trustedOrigins:[origin],
32
+ plugins:[{id:'spl-dsm-native',endpoints:{nativeLogin:createAuthEndpoint('/native-login',{method:'POST'},async ctx=>{
33
+ const p=consume(ctx.request?.headers.get('x-native-ticket'));
34
+ let user=store.db.user.find(u=>u.id===store.bindings[key(p)]);
35
+ if(!user){user=await ctx.context.internalAdapter.createUser({name:p.name,email:`${createHash('sha256').update(key(p)).digest('hex')}@identity.invalid`,emailVerified:false});store.bindings[key(p)]=user.id;}
36
+ const session=await ctx.context.internalAdapter.createSession(user.id);
37
+ await setSessionCookie(ctx,{session,user});
38
+ return ctx.json({user:p.name});
39
+ })}}],
40
+ });
41
+ return {async handle(method,value,cookie=''){
42
+ const headers=new Headers({'content-type':'application/json',origin,'x-native-ticket':value||'',cookie});
43
+ if(method==='login'){
44
+ const r=await auth.handler(new Request(`${origin}/api/auth/native-login`,{method:'POST',headers,body:'{}'}));
45
+ return {status:r.status,cookies:r.headers.getSetCookie(),body:await r.json()};
46
+ }
47
+ let native;try{native=consume(value)}catch{return {status:401,cookies:[],body:{error:'native_ticket_required'}}}
48
+ const session=await auth.api.getSession({headers});
49
+ if(!session)return {status:401,cookies:[],body:{error:'app_session_required'}};
50
+ if(store.bindings[key(native)]!==session.user.id)return {status:403,cookies:[],body:{error:'principal_mismatch'}};
51
+ if(method==='current')return {status:200,cookies:[],body:{user:native.name,expiresAt:session.session.expiresAt}};
52
+ if(method==='logout'){
53
+ const r=await auth.handler(new Request(`${origin}/api/auth/sign-out`,{method:'POST',headers,body:'{}'}));
54
+ return {status:r.status,cookies:r.headers.getSetCookie(),body:await r.json()};
55
+ }
56
+ return {status:400,cookies:[],body:{error:'invalid_method'}};
57
+ }};
58
+ }
59
+ export async function run(input){
60
+ const {packageID,store,ticket,principal,method,cookie}=input;
61
+ if(!/^[A-Za-z][A-Za-z0-9]{1,63}$/.test(packageID)||!['login','current','logout'].includes(method))throw Error('invalid_input');
62
+ const app=makeApp({packageID,store,ticket,principal});
63
+ const response=await app.handle(method,ticket,cookie);
64
+ if(response.status!==200)return {store,result:{ok:false,error:response.body.error||response.body.message||'identity_refused'}};
65
+ return {store,result:{ok:true,user:principal.name,cookie:response.cookies.map(v=>v.split(';')[0]).join('; '),expiresAt:response.body.expiresAt}};
66
+ }
67
+ if(process.argv[1] && realpathSync(process.argv[1])===fileURLToPath(import.meta.url)){
68
+ try {
69
+ let body='';for await(const chunk of process.stdin){body+=chunk;if(Buffer.byteLength(body)>1048576)throw Error('oversized')}
70
+ process.stdout.write(JSON.stringify(await run(JSON.parse(body))));
71
+ }catch{process.stderr.write('identity_refused\n');process.exitCode=1}
72
+ }
@@ -0,0 +1,2 @@
1
+ import {build} from 'esbuild';
2
+ await build({entryPoints:['app.mjs'],bundle:true,platform:'node',format:'esm',target:'node22',outfile:'dist/app.mjs',banner:{js:"import {createRequire} from 'node:module'; const require = createRequire(import.meta.url);"}});