@synoikia/core 0.1.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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app.d.ts +79 -0
  3. package/dist/app.js +155 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/approvals/links.d.ts +23 -0
  6. package/dist/approvals/links.js +49 -0
  7. package/dist/approvals/links.js.map +1 -0
  8. package/dist/approvals/service.d.ts +110 -0
  9. package/dist/approvals/service.js +182 -0
  10. package/dist/approvals/service.js.map +1 -0
  11. package/dist/audit-query.d.ts +52 -0
  12. package/dist/audit-query.js +116 -0
  13. package/dist/audit-query.js.map +1 -0
  14. package/dist/audit.d.ts +5 -0
  15. package/dist/audit.js +8 -0
  16. package/dist/audit.js.map +1 -0
  17. package/dist/auth/mcp-auth.d.ts +50 -0
  18. package/dist/auth/mcp-auth.js +160 -0
  19. package/dist/auth/mcp-auth.js.map +1 -0
  20. package/dist/auth/mcp-tokens.d.ts +57 -0
  21. package/dist/auth/mcp-tokens.js +143 -0
  22. package/dist/auth/mcp-tokens.js.map +1 -0
  23. package/dist/auth/oauth.d.ts +188 -0
  24. package/dist/auth/oauth.js +470 -0
  25. package/dist/auth/oauth.js.map +1 -0
  26. package/dist/auth/oidc.d.ts +110 -0
  27. package/dist/auth/oidc.js +221 -0
  28. package/dist/auth/oidc.js.map +1 -0
  29. package/dist/auth/sessions.d.ts +34 -0
  30. package/dist/auth/sessions.js +83 -0
  31. package/dist/auth/sessions.js.map +1 -0
  32. package/dist/auth/throttle.d.ts +31 -0
  33. package/dist/auth/throttle.js +52 -0
  34. package/dist/auth/throttle.js.map +1 -0
  35. package/dist/auth/tokens.d.ts +7 -0
  36. package/dist/auth/tokens.js +40 -0
  37. package/dist/auth/tokens.js.map +1 -0
  38. package/dist/auth/totp.d.ts +14 -0
  39. package/dist/auth/totp.js +81 -0
  40. package/dist/auth/totp.js.map +1 -0
  41. package/dist/auth/users.d.ts +70 -0
  42. package/dist/auth/users.js +296 -0
  43. package/dist/auth/users.js.map +1 -0
  44. package/dist/catalog/groups.d.ts +102 -0
  45. package/dist/catalog/groups.js +363 -0
  46. package/dist/catalog/groups.js.map +1 -0
  47. package/dist/catalog/registry.d.ts +25 -0
  48. package/dist/catalog/registry.js +80 -0
  49. package/dist/catalog/registry.js.map +1 -0
  50. package/dist/catalog/rules.d.ts +128 -0
  51. package/dist/catalog/rules.js +178 -0
  52. package/dist/catalog/rules.js.map +1 -0
  53. package/dist/catalog/sync.d.ts +29 -0
  54. package/dist/catalog/sync.js +222 -0
  55. package/dist/catalog/sync.js.map +1 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +83 -0
  58. package/dist/cli.js.map +1 -0
  59. package/dist/config/env.d.ts +40 -0
  60. package/dist/config/env.js +71 -0
  61. package/dist/config/env.js.map +1 -0
  62. package/dist/crypto/index.d.ts +28 -0
  63. package/dist/crypto/index.js +88 -0
  64. package/dist/crypto/index.js.map +1 -0
  65. package/dist/crypto/rotate.d.ts +15 -0
  66. package/dist/crypto/rotate.js +61 -0
  67. package/dist/crypto/rotate.js.map +1 -0
  68. package/dist/db/index.d.ts +17 -0
  69. package/dist/db/index.js +29 -0
  70. package/dist/db/index.js.map +1 -0
  71. package/dist/db/schema.d.ts +4389 -0
  72. package/dist/db/schema.js +372 -0
  73. package/dist/db/schema.js.map +1 -0
  74. package/dist/endpoints/slug.d.ts +3 -0
  75. package/dist/endpoints/slug.js +7 -0
  76. package/dist/endpoints/slug.js.map +1 -0
  77. package/dist/errors.d.ts +16 -0
  78. package/dist/errors.js +29 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/events.d.ts +43 -0
  81. package/dist/events.js +16 -0
  82. package/dist/events.js.map +1 -0
  83. package/dist/gate/access.d.ts +47 -0
  84. package/dist/gate/access.js +48 -0
  85. package/dist/gate/access.js.map +1 -0
  86. package/dist/gate/attestation.d.ts +23 -0
  87. package/dist/gate/attestation.js +33 -0
  88. package/dist/gate/attestation.js.map +1 -0
  89. package/dist/gate/canonical.d.ts +3 -0
  90. package/dist/gate/canonical.js +11 -0
  91. package/dist/gate/canonical.js.map +1 -0
  92. package/dist/gate/match.d.ts +67 -0
  93. package/dist/gate/match.js +133 -0
  94. package/dist/gate/match.js.map +1 -0
  95. package/dist/gate/pipeline.d.ts +61 -0
  96. package/dist/gate/pipeline.js +284 -0
  97. package/dist/gate/pipeline.js.map +1 -0
  98. package/dist/gate/preapproval.d.ts +24 -0
  99. package/dist/gate/preapproval.js +48 -0
  100. package/dist/gate/preapproval.js.map +1 -0
  101. package/dist/gate/rate-limit.d.ts +35 -0
  102. package/dist/gate/rate-limit.js +82 -0
  103. package/dist/gate/rate-limit.js.map +1 -0
  104. package/dist/gate/redact.d.ts +23 -0
  105. package/dist/gate/redact.js +87 -0
  106. package/dist/gate/redact.js.map +1 -0
  107. package/dist/http/admin/auth.d.ts +33 -0
  108. package/dist/http/admin/auth.js +338 -0
  109. package/dist/http/admin/auth.js.map +1 -0
  110. package/dist/http/admin/instances.d.ts +5 -0
  111. package/dist/http/admin/instances.js +193 -0
  112. package/dist/http/admin/instances.js.map +1 -0
  113. package/dist/http/admin/system.d.ts +5 -0
  114. package/dist/http/admin/system.js +225 -0
  115. package/dist/http/admin/system.js.map +1 -0
  116. package/dist/http/admin-app.d.ts +9 -0
  117. package/dist/http/admin-app.js +51 -0
  118. package/dist/http/admin-app.js.map +1 -0
  119. package/dist/http/common.d.ts +32 -0
  120. package/dist/http/common.js +80 -0
  121. package/dist/http/common.js.map +1 -0
  122. package/dist/http/mcp/approval-routes.d.ts +11 -0
  123. package/dist/http/mcp/approval-routes.js +117 -0
  124. package/dist/http/mcp/approval-routes.js.map +1 -0
  125. package/dist/http/mcp/endpoint.d.ts +32 -0
  126. package/dist/http/mcp/endpoint.js +293 -0
  127. package/dist/http/mcp/endpoint.js.map +1 -0
  128. package/dist/http/mcp/oauth-routes.d.ts +29 -0
  129. package/dist/http/mcp/oauth-routes.js +465 -0
  130. package/dist/http/mcp/oauth-routes.js.map +1 -0
  131. package/dist/http/mcp/pages.d.ts +63 -0
  132. package/dist/http/mcp/pages.js +200 -0
  133. package/dist/http/mcp/pages.js.map +1 -0
  134. package/dist/http/mcp-app.d.ts +7 -0
  135. package/dist/http/mcp-app.js +30 -0
  136. package/dist/http/mcp-app.js.map +1 -0
  137. package/dist/instances/connection.d.ts +37 -0
  138. package/dist/instances/connection.js +109 -0
  139. package/dist/instances/connection.js.map +1 -0
  140. package/dist/instances/manager.d.ts +313 -0
  141. package/dist/instances/manager.js +600 -0
  142. package/dist/instances/manager.js.map +1 -0
  143. package/dist/instances/settings.d.ts +42 -0
  144. package/dist/instances/settings.js +57 -0
  145. package/dist/instances/settings.js.map +1 -0
  146. package/dist/lenient.d.ts +16 -0
  147. package/dist/lenient.js +51 -0
  148. package/dist/lenient.js.map +1 -0
  149. package/dist/lock.d.ts +28 -0
  150. package/dist/lock.js +77 -0
  151. package/dist/lock.js.map +1 -0
  152. package/dist/main.d.ts +1 -0
  153. package/dist/main.js +38 -0
  154. package/dist/main.js.map +1 -0
  155. package/dist/maintenance.d.ts +10 -0
  156. package/dist/maintenance.js +48 -0
  157. package/dist/maintenance.js.map +1 -0
  158. package/dist/notify/service.d.ts +149 -0
  159. package/dist/notify/service.js +358 -0
  160. package/dist/notify/service.js.map +1 -0
  161. package/dist/plugins/discovery.d.ts +39 -0
  162. package/dist/plugins/discovery.js +157 -0
  163. package/dist/plugins/discovery.js.map +1 -0
  164. package/dist/plugins/minisign.d.ts +25 -0
  165. package/dist/plugins/minisign.js +49 -0
  166. package/dist/plugins/minisign.js.map +1 -0
  167. package/dist/plugins/process.d.ts +55 -0
  168. package/dist/plugins/process.js +162 -0
  169. package/dist/plugins/process.js.map +1 -0
  170. package/dist/plugins/repos.d.ts +198 -0
  171. package/dist/plugins/repos.js +612 -0
  172. package/dist/plugins/repos.js.map +1 -0
  173. package/dist/plugins/supervisor.d.ts +50 -0
  174. package/dist/plugins/supervisor.js +125 -0
  175. package/dist/plugins/supervisor.js.map +1 -0
  176. package/dist/runtime/index.d.ts +28 -0
  177. package/dist/runtime/index.js +248 -0
  178. package/dist/runtime/index.js.map +1 -0
  179. package/dist/sandbox/index.d.ts +53 -0
  180. package/dist/sandbox/index.js +197 -0
  181. package/dist/sandbox/index.js.map +1 -0
  182. package/dist/server.d.ts +16 -0
  183. package/dist/server.js +36 -0
  184. package/dist/server.js.map +1 -0
  185. package/dist/settings.d.ts +43 -0
  186. package/dist/settings.js +99 -0
  187. package/dist/settings.js.map +1 -0
  188. package/dist/testing/index.d.ts +73 -0
  189. package/dist/testing/index.js +174 -0
  190. package/dist/testing/index.js.map +1 -0
  191. package/dist/version.d.ts +2 -0
  192. package/dist/version.js +4 -0
  193. package/dist/version.js.map +1 -0
  194. package/drizzle/0000_init.sql +334 -0
  195. package/drizzle/0001_totp_last_step.sql +1 -0
  196. package/drizzle/0002_access_levels.sql +16 -0
  197. package/drizzle/0003_drop_exclusion_flags.sql +2 -0
  198. package/drizzle/0004_grant_instance_ids.sql +1 -0
  199. package/drizzle/0005_rule_strict_miss.sql +1 -0
  200. package/drizzle/0006_attestation_waived.sql +1 -0
  201. package/drizzle/meta/0000_snapshot.json +2318 -0
  202. package/drizzle/meta/0001_snapshot.json +2325 -0
  203. package/drizzle/meta/0002_snapshot.json +2348 -0
  204. package/drizzle/meta/0003_snapshot.json +2332 -0
  205. package/drizzle/meta/0004_snapshot.json +2339 -0
  206. package/drizzle/meta/0005_snapshot.json +2346 -0
  207. package/drizzle/meta/0006_snapshot.json +2354 -0
  208. package/drizzle/meta/_journal.json +55 -0
  209. package/package.json +58 -0
@@ -0,0 +1,71 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { z } from 'zod';
4
+ const here = path.dirname(fileURLToPath(import.meta.url));
5
+ // src/config or dist/config → repository/workspace root is three levels up.
6
+ const workspaceRoot = path.resolve(here, '../../../..');
7
+ const bool = z
8
+ .enum(['true', 'false', '1', '0', ''])
9
+ .optional()
10
+ .transform((v) => v === 'true' || v === '1');
11
+ const port = z.coerce.number().int().min(0).max(65535);
12
+ const optionalUrl = z
13
+ .string()
14
+ .optional()
15
+ .transform((v) => (v ? v : undefined))
16
+ .pipe(z.url().optional());
17
+ const EnvSchema = z
18
+ .object({
19
+ DATA_DIR: z.string().default('/data'),
20
+ MASTER_KEY: z.string().optional(),
21
+ MCP_HOST: z.string().default('0.0.0.0'),
22
+ MCP_PORT: port.default(8080),
23
+ ADMIN_HOST: z.string().default('0.0.0.0'),
24
+ ADMIN_PORT: port.default(8081),
25
+ PUBLIC_MCP_URL: optionalUrl,
26
+ PUBLIC_ADMIN_URL: optionalUrl,
27
+ /**
28
+ * How many reverse proxies in front of core to trust: `true`/`1` for one, `2` for two chained, and so
29
+ * on. X-Forwarded-For is read from the right, skipping only what those proxies appended.
30
+ */
31
+ TRUST_PROXY: z
32
+ .string()
33
+ .optional()
34
+ .transform((v, ctx) => {
35
+ const s = (v ?? '').trim().toLowerCase();
36
+ if (s === '' || s === 'false')
37
+ return 0;
38
+ if (s === 'true')
39
+ return 1;
40
+ if (/^\d+$/.test(s) && Number(s) <= 10)
41
+ return Number(s);
42
+ ctx.addIssue({ code: 'custom', message: 'TRUST_PROXY must be true, false or a hop count (0–10)' });
43
+ return z.NEVER;
44
+ }),
45
+ /**
46
+ * Extra host names the MCP listener answers to (comma-separated), besides PUBLIC_MCP_URL's host,
47
+ * localhost and IP addresses. Anything else is refused, which stops DNS rebinding.
48
+ */
49
+ MCP_ALLOWED_HOSTS: z
50
+ .string()
51
+ .optional()
52
+ .transform((v) => (v ?? '')
53
+ .split(',')
54
+ .map((h) => h.trim().toLowerCase())
55
+ .filter(Boolean)),
56
+ CORE_PLUGINS_AUTOENABLE: bool,
57
+ CORE_PLUGINS_DIR: z.string().default(path.join(workspaceRoot, 'plugins')),
58
+ ADMIN_UI_DIR: z.string().default(path.join(workspaceRoot, 'packages/admin-ui/dist')),
59
+ ADMIN_BOOTSTRAP_USERNAME: z.string().optional(),
60
+ ADMIN_BOOTSTRAP_PASSWORD: z.string().optional(),
61
+ ADMIN_FORCE_LOCAL_LOGIN: bool,
62
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
63
+ })
64
+ .refine((e) => e.MCP_PORT === 0 || e.MCP_PORT !== e.ADMIN_PORT, {
65
+ message: 'MCP_PORT and ADMIN_PORT must differ',
66
+ path: ['ADMIN_PORT'],
67
+ });
68
+ export function loadConfig(env = process.env) {
69
+ return EnvSchema.parse(env);
70
+ }
71
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export declare const MASTER_KEY_FILENAME = "master.key";
2
+ export declare class DecryptionError extends Error {
3
+ constructor(message?: string);
4
+ }
5
+ /** Associated data for a stored secret, e.g. `aad('plugin_instances', 'secrets_enc', id)`. */
6
+ export declare function aad(table: string, column: string, rowId: string): string;
7
+ export declare class SecretBox {
8
+ private readonly key;
9
+ private constructor();
10
+ static fromKey(key: Buffer): SecretBox;
11
+ encrypt(plaintext: string | Buffer, associatedData: string): Buffer;
12
+ decrypt(blob: Buffer, associatedData: string): Buffer;
13
+ encryptJson(value: unknown, associatedData: string): Buffer;
14
+ decryptJson<T = unknown>(blob: Buffer, associatedData: string): T;
15
+ /** Purpose-bound subkeys (attestation HMAC, approval links, session pepper) via HKDF-SHA256. */
16
+ deriveKey(purpose: string, length?: number): Buffer;
17
+ }
18
+ export interface MasterKeyResult {
19
+ key: Buffer;
20
+ source: 'env' | 'file' | 'generated';
21
+ /** Set when the key lives in the data volume, which the design recommends against for production. */
22
+ warning?: string;
23
+ }
24
+ /** `MASTER_KEY` env wins; otherwise read or create `DATA_DIR/master.key` with mode 0600. */
25
+ export declare function loadMasterKey(opts: {
26
+ envKey?: string;
27
+ dataDir: string;
28
+ }): MasterKeyResult;
@@ -0,0 +1,88 @@
1
+ import { createCipheriv, createDecipheriv, hkdfSync, randomBytes } from 'node:crypto';
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ /**
5
+ * Application-level encryption for secrets at rest (design §7.2): AES-256-GCM with a random 96-bit
6
+ * nonce per value and associated data binding each ciphertext to its table/column/row.
7
+ */
8
+ const VERSION = 0x01;
9
+ const NONCE_BYTES = 12;
10
+ const TAG_BYTES = 16;
11
+ const KEY_BYTES = 32;
12
+ export const MASTER_KEY_FILENAME = 'master.key';
13
+ export class DecryptionError extends Error {
14
+ constructor(message = 'Unable to decrypt value (wrong key, wrong row binding, or corrupted data)') {
15
+ super(message);
16
+ this.name = 'DecryptionError';
17
+ }
18
+ }
19
+ /** Associated data for a stored secret, e.g. `aad('plugin_instances', 'secrets_enc', id)`. */
20
+ export function aad(table, column, rowId) {
21
+ return `${table}:${column}:${rowId}`;
22
+ }
23
+ export class SecretBox {
24
+ key;
25
+ constructor(key) {
26
+ this.key = key;
27
+ }
28
+ static fromKey(key) {
29
+ if (key.length !== KEY_BYTES)
30
+ throw new Error(`Master key must be ${KEY_BYTES} bytes, got ${key.length}`);
31
+ return new SecretBox(Buffer.from(key));
32
+ }
33
+ encrypt(plaintext, associatedData) {
34
+ const nonce = randomBytes(NONCE_BYTES);
35
+ const cipher = createCipheriv('aes-256-gcm', this.key, nonce);
36
+ cipher.setAAD(Buffer.from(associatedData, 'utf8'));
37
+ const body = Buffer.concat([cipher.update(plaintext), cipher.final()]);
38
+ return Buffer.concat([Buffer.of(VERSION), nonce, body, cipher.getAuthTag()]);
39
+ }
40
+ decrypt(blob, associatedData) {
41
+ if (blob.length < 1 + NONCE_BYTES + TAG_BYTES || blob[0] !== VERSION)
42
+ throw new DecryptionError();
43
+ const nonce = blob.subarray(1, 1 + NONCE_BYTES);
44
+ const tag = blob.subarray(blob.length - TAG_BYTES);
45
+ const body = blob.subarray(1 + NONCE_BYTES, blob.length - TAG_BYTES);
46
+ try {
47
+ const decipher = createDecipheriv('aes-256-gcm', this.key, nonce);
48
+ decipher.setAAD(Buffer.from(associatedData, 'utf8'));
49
+ decipher.setAuthTag(tag);
50
+ return Buffer.concat([decipher.update(body), decipher.final()]);
51
+ }
52
+ catch {
53
+ throw new DecryptionError();
54
+ }
55
+ }
56
+ encryptJson(value, associatedData) {
57
+ return this.encrypt(JSON.stringify(value), associatedData);
58
+ }
59
+ decryptJson(blob, associatedData) {
60
+ return JSON.parse(this.decrypt(blob, associatedData).toString('utf8'));
61
+ }
62
+ /** Purpose-bound subkeys (attestation HMAC, approval links, session pepper) via HKDF-SHA256. */
63
+ deriveKey(purpose, length = KEY_BYTES) {
64
+ return Buffer.from(hkdfSync('sha256', this.key, Buffer.alloc(0), `synoikia:${purpose}`, length));
65
+ }
66
+ }
67
+ function decodeKey(raw, origin) {
68
+ const key = Buffer.from(raw.trim(), 'base64');
69
+ if (key.length !== KEY_BYTES) {
70
+ throw new Error(`${origin} must be ${KEY_BYTES} bytes of base64 (e.g. \`openssl rand -base64 32\`)`);
71
+ }
72
+ return key;
73
+ }
74
+ /** `MASTER_KEY` env wins; otherwise read or create `DATA_DIR/master.key` with mode 0600. */
75
+ export function loadMasterKey(opts) {
76
+ if (opts.envKey)
77
+ return { key: decodeKey(opts.envKey, 'MASTER_KEY'), source: 'env' };
78
+ const file = path.join(opts.dataDir, MASTER_KEY_FILENAME);
79
+ const warning = `Master key is stored at ${file}, inside the data volume. Set MASTER_KEY to keep it separate from the database.`;
80
+ if (existsSync(file))
81
+ return { key: decodeKey(readFileSync(file, 'utf8'), file), source: 'file', warning };
82
+ mkdirSync(opts.dataDir, { recursive: true });
83
+ const key = randomBytes(KEY_BYTES);
84
+ writeFileSync(file, `${key.toString('base64')}\n`, { mode: 0o600, flag: 'wx' });
85
+ chmodSync(file, 0o600);
86
+ return { key, source: 'generated', warning };
87
+ }
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Db } from '../db/index.js';
2
+ import type { SecretBox } from './index.js';
3
+ /**
4
+ * Master-key rotation (design §7.2): re-encrypts every secret column under the new key in one
5
+ * transaction. Everything is decrypted first, so a wrong current key changes nothing. Sessions are
6
+ * cleared because their ids are peppered with a key derived from the master key.
7
+ */
8
+ export interface RotationResult {
9
+ users: number;
10
+ oidc: number;
11
+ instances: number;
12
+ notifiers: number;
13
+ sessionsCleared: number;
14
+ }
15
+ export declare function rotateSecrets(db: Db, from: SecretBox, to: SecretBox): RotationResult;
@@ -0,0 +1,61 @@
1
+ import { eq } from 'drizzle-orm';
2
+ import { notifierChannels, oidcConfig, pluginInstances, sessions, users } from '../db/schema.js';
3
+ import { aad } from './index.js';
4
+ export function rotateSecrets(db, from, to) {
5
+ const reencrypt = (blob, table, column, id) => {
6
+ const ad = aad(table, column, id);
7
+ return to.encrypt(from.decrypt(blob, ad), ad);
8
+ };
9
+ return db.transaction((tx) => {
10
+ const userRows = tx
11
+ .select()
12
+ .from(users)
13
+ .all()
14
+ .filter((u) => u.totpSecretEnc);
15
+ const oidcRows = tx
16
+ .select()
17
+ .from(oidcConfig)
18
+ .all()
19
+ .filter((o) => o.clientSecretEnc);
20
+ const instanceRows = tx
21
+ .select()
22
+ .from(pluginInstances)
23
+ .all()
24
+ .filter((i) => i.secretsEnc);
25
+ const notifierRows = tx
26
+ .select()
27
+ .from(notifierChannels)
28
+ .all()
29
+ .filter((n) => n.secretsEnc);
30
+ // Decrypt-and-re-encrypt everything before writing anything.
31
+ const updates = [
32
+ ...userRows.map((u) => {
33
+ const v = reencrypt(u.totpSecretEnc, 'users', 'totp_secret_enc', u.id);
34
+ return () => tx.update(users).set({ totpSecretEnc: v }).where(eq(users.id, u.id)).run();
35
+ }),
36
+ ...oidcRows.map((o) => {
37
+ const v = reencrypt(o.clientSecretEnc, 'oidc_config', 'client_secret_enc', String(o.id));
38
+ return () => tx.update(oidcConfig).set({ clientSecretEnc: v }).where(eq(oidcConfig.id, o.id)).run();
39
+ }),
40
+ ...instanceRows.map((i) => {
41
+ const v = reencrypt(i.secretsEnc, 'plugin_instances', 'secrets_enc', i.id);
42
+ return () => tx.update(pluginInstances).set({ secretsEnc: v }).where(eq(pluginInstances.id, i.id)).run();
43
+ }),
44
+ ...notifierRows.map((n) => {
45
+ const v = reencrypt(n.secretsEnc, 'notifier_channels', 'secrets_enc', n.id);
46
+ return () => tx.update(notifierChannels).set({ secretsEnc: v }).where(eq(notifierChannels.id, n.id)).run();
47
+ }),
48
+ ];
49
+ for (const apply of updates)
50
+ apply();
51
+ const sessionsCleared = tx.delete(sessions).run().changes;
52
+ return {
53
+ users: userRows.length,
54
+ oidc: oidcRows.length,
55
+ instances: instanceRows.length,
56
+ notifiers: notifierRows.length,
57
+ sessionsCleared,
58
+ };
59
+ });
60
+ }
61
+ //# sourceMappingURL=rotate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotate.js","sourceRoot":"","sources":["../../src/crypto/rotate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
3
+ import * as schema from './schema.js';
4
+ export type Db = BetterSQLite3Database<typeof schema> & {
5
+ $client: Database.Database;
6
+ };
7
+ /** A transaction handle; services accept either so callers can compose them in one transaction. */
8
+ export type Tx = Parameters<Parameters<Db['transaction']>[0]>[0];
9
+ export type DbLike = Db | Tx;
10
+ export declare const DB_FILENAME = "synoikia.sqlite";
11
+ /**
12
+ * Opens (creating if needed) the single SQLite database and applies pending migrations
13
+ * (design §7). Pass `':memory:'` for tests.
14
+ */
15
+ export declare function openDatabase(location: {
16
+ dataDir: string;
17
+ } | ':memory:'): Db;
@@ -0,0 +1,29 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import Database from 'better-sqlite3';
5
+ import { drizzle } from 'drizzle-orm/better-sqlite3';
6
+ import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
7
+ import * as schema from './schema.js';
8
+ // src/db or dist/db → packages/core/drizzle
9
+ const MIGRATIONS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../drizzle');
10
+ export const DB_FILENAME = 'synoikia.sqlite';
11
+ /**
12
+ * Opens (creating if needed) the single SQLite database and applies pending migrations
13
+ * (design §7). Pass `':memory:'` for tests.
14
+ */
15
+ export function openDatabase(location) {
16
+ let file = ':memory:';
17
+ if (location !== ':memory:') {
18
+ mkdirSync(location.dataDir, { recursive: true });
19
+ file = path.join(location.dataDir, DB_FILENAME);
20
+ }
21
+ const sqlite = new Database(file);
22
+ sqlite.pragma('journal_mode = WAL');
23
+ sqlite.pragma('foreign_keys = ON');
24
+ sqlite.pragma('busy_timeout = 5000');
25
+ const db = drizzle(sqlite, { schema });
26
+ migrate(db, { migrationsFolder: MIGRATIONS_DIR });
27
+ return db;
28
+ }
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAErD,OAAO,EAAE,OAAO,EAAE,MAAM,qCAAqC,CAAC;AAC9D,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAOtC,4CAA4C;AAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;AAEnG,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAA0C;IACrE,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC5B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,OAAO,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC;AACZ,CAAC"}