@ibgib/space-gib 0.0.1
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/CHANGELOG.md +31 -0
- package/Dockerfile +14 -0
- package/IMPLEMENTATION.md +484 -0
- package/README.md +46 -0
- package/dist/client/bootstrap.mjs +58 -0
- package/dist/client/bootstrap.mjs.map +7 -0
- package/dist/client/chunk-CT47Z5WU.mjs +21 -0
- package/dist/client/chunk-CT47Z5WU.mjs.map +7 -0
- package/dist/client/chunk-RHEDTRKF.mjs +235 -0
- package/dist/client/chunk-RHEDTRKF.mjs.map +7 -0
- package/dist/client/index.html +147 -0
- package/dist/client/index.mjs +2 -0
- package/dist/client/index.mjs.map +7 -0
- package/dist/client/script.mjs +2 -0
- package/dist/client/script.mjs.map +7 -0
- package/dist/client/style.css +605 -0
- package/dist/respec-gib.node.mjs +5 -0
- package/dist/server/server.mjs +20157 -0
- package/dist/server/server.mjs.map +7 -0
- package/generate-version-file.js +35 -0
- package/package.json +27 -0
- package/src/client/AUTO-GENERATED-version.mts +11 -0
- package/src/client/README.md +19 -0
- package/src/client/api/function-infos.web.mts +38 -0
- package/src/client/api/space-gib-api-bridge.mts +85 -0
- package/src/client/bootstrap.mts +49 -0
- package/src/client/components/keystone-creator/keystone-creator.css +139 -0
- package/src/client/components/keystone-creator/keystone-creator.html +26 -0
- package/src/client/components/keystone-creator/keystone-creator.mts +229 -0
- package/src/client/constants.mts +76 -0
- package/src/client/custom.d.ts +11 -0
- package/src/client/dev-tools.mts +540 -0
- package/src/client/helpers.web.mts +178 -0
- package/src/client/index.html +147 -0
- package/src/client/index.mts +59 -0
- package/src/client/script.mts +13 -0
- package/src/client/style.css +605 -0
- package/src/client/types.mts +85 -0
- package/src/client/ui/shell/space-gib-shell-constants.mts +24 -0
- package/src/client/ui/shell/space-gib-shell-service.mts +233 -0
- package/src/client/ui/shell/space-gib-shell-types.mts +5 -0
- package/src/client/witness/app/space-gib/space-gib-app-v1.mts +160 -0
- package/src/client/witness/app/space-gib/space-gib-constants.mts +38 -0
- package/src/client/witness/app/space-gib/space-gib-helper.mts +72 -0
- package/src/client/witness/app/space-gib/space-gib-types.mts +47 -0
- package/src/common/keystone-policies.mts +159 -0
- package/src/respec-gib.node.mts +6 -0
- package/src/server/README.md +18 -0
- package/src/server/bootstrap-helper.mts +141 -0
- package/src/server/bootstrap-helper.respec.mts +100 -0
- package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +85 -0
- package/src/server/path-constants.mts +89 -0
- package/src/server/path-helper.mts +101 -0
- package/src/server/path-helper.respec.mts +94 -0
- package/src/server/serve-gib/CHANGELOG.md +29 -0
- package/src/server/serve-gib/README.md +34 -0
- package/src/server/serve-gib/constants.mts +1 -0
- package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +104 -0
- package/src/server/serve-gib/handlers/api/health.handler.mts +23 -0
- package/src/server/serve-gib/handlers/api/health.respec.mts +51 -0
- package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +49 -0
- package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +176 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +261 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +146 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +198 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +107 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +29 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +70 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +130 -0
- package/src/server/serve-gib/handlers/error-handler.mts +36 -0
- package/src/server/serve-gib/handlers/handler-base.mts +383 -0
- package/src/server/serve-gib/handlers/static-handler.mts +82 -0
- package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +498 -0
- package/src/server/serve-gib/handlers/ws/ws-helper.mts +111 -0
- package/src/server/serve-gib/handlers/ws/ws-types.mts +53 -0
- package/src/server/serve-gib/serve-gib-helpers.mts +32 -0
- package/src/server/serve-gib/serve-gib-v1.mts +172 -0
- package/src/server/serve-gib/serve-gib.respec.mts +90 -0
- package/src/server/serve-gib/types.mts +102 -0
- package/src/server/server-constants.mts +2 -0
- package/src/server/server.mts +96 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +29 -0
- package/tsconfig.test.json +27 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module common/keystone-policies
|
|
3
|
+
*
|
|
4
|
+
* Centralized identity and security policies for the space-gib application.
|
|
5
|
+
* Defines standard configurations for both persistent (Domain) and ephemeral (Session) keystones.
|
|
6
|
+
*
|
|
7
|
+
* ## important
|
|
8
|
+
*
|
|
9
|
+
* This file is part of the `src/common` layer. It MUST NEVER import from
|
|
10
|
+
* `src/client` or `src/server`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { HashAlgorithm } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
14
|
+
import {
|
|
15
|
+
KeystoneReplenishStrategy,
|
|
16
|
+
KeystoneChallengeType,
|
|
17
|
+
KeystoneIbGib_V1,
|
|
18
|
+
KeystoneSolution
|
|
19
|
+
} from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
|
|
20
|
+
import { KeystoneStrategyFactory } from '@ibgib/core-gib/dist/keystone/strategy/keystone-strategy-factory.mjs';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// 1. Session Keystone (S) Policies
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default configurations for short-lived session keystones (S^Stjp).
|
|
28
|
+
* These values match the real-world usage in the space-gib client.
|
|
29
|
+
*/
|
|
30
|
+
export const SESSION_KEYSTONE_POLICY = {
|
|
31
|
+
/**
|
|
32
|
+
* Shared parameters across all session pools
|
|
33
|
+
*/
|
|
34
|
+
COMMON: {
|
|
35
|
+
ALGO: HashAlgorithm.sha_256,
|
|
36
|
+
TYPE: KeystoneChallengeType.hash_reveal_v1,
|
|
37
|
+
ROUNDS: 2,
|
|
38
|
+
REPLENISH: KeystoneReplenishStrategy.topUp,
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The primary authorization pool for sync actions (e.g. putting/getting ibgibs).
|
|
43
|
+
*/
|
|
44
|
+
DEFAULT_POOL: {
|
|
45
|
+
ID: 'default',
|
|
46
|
+
SIZE: 20,
|
|
47
|
+
SELECT_SEQUENTIALLY: 2,
|
|
48
|
+
SELECT_RANDOMLY: 2,
|
|
49
|
+
/**
|
|
50
|
+
* We want more for this, with future implementations we will not have
|
|
51
|
+
* resource exhaustion.
|
|
52
|
+
*/
|
|
53
|
+
TARGET_BINDING_CHARS: 3,
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Dedicated pool for the WebSocket sync protocol handshake.
|
|
58
|
+
*/
|
|
59
|
+
HANDSHAKE_POOL: {
|
|
60
|
+
ID: 'handshake',
|
|
61
|
+
VERB: 'handshake',
|
|
62
|
+
SIZE: 10,
|
|
63
|
+
SELECT_SEQUENTIALLY: 2,
|
|
64
|
+
SELECT_RANDOMLY: 2,
|
|
65
|
+
TARGET_BINDING_CHARS: 0,
|
|
66
|
+
/** Number of IDs the server should demand during the handshake. */
|
|
67
|
+
SERVER_DEMAND_COUNT: 3,
|
|
68
|
+
}
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// 2. Domain/Identity Keystone (I) Policies
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Server-side minimum requirements for long-lived primary identity keystones.
|
|
78
|
+
*
|
|
79
|
+
* [Nag] These are initial placeholder standards and should be reviewed for
|
|
80
|
+
* production hardening.
|
|
81
|
+
*/
|
|
82
|
+
export const DOMAIN_KEYSTONE_SECURITY_STANDARDS = {
|
|
83
|
+
MIN_DEFAULT_POOL_SIZE: 50,
|
|
84
|
+
ALLOWED_ALGORITHMS: [
|
|
85
|
+
HashAlgorithm.sha_256,
|
|
86
|
+
HashAlgorithm.sha_512
|
|
87
|
+
],
|
|
88
|
+
} as const;
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// 3. Upfront Picket-Fence Handshake Helpers
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns the lexicographically first challenge from the session keystone's
|
|
97
|
+
* 'handshake' pool to be used as a deterministic, dynamic upfront pre-filter challenge.
|
|
98
|
+
*/
|
|
99
|
+
export function getHandshakeChallenge(keystone: KeystoneIbGib_V1): { challengeId: string; challenge: any } {
|
|
100
|
+
const handshakePool = (keystone.data?.challengePools ?? []).find(
|
|
101
|
+
(p) => p.id === SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID
|
|
102
|
+
);
|
|
103
|
+
if (!handshakePool) {
|
|
104
|
+
throw new Error(`Keystone missing "${SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID}" pool`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const challengeIds = Object.keys(handshakePool.challenges).sort();
|
|
108
|
+
if (challengeIds.length === 0) {
|
|
109
|
+
throw new Error(`Handshake pool has no challenges`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const challengeId = challengeIds[0];
|
|
113
|
+
const challenge = handshakePool.challenges[challengeId];
|
|
114
|
+
return { challengeId, challenge };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Cryptographically validates the upfront picket-fence solution against the session keystone.
|
|
119
|
+
* Verifies that the client solved the lexicographically first challenge from the 'handshake' pool.
|
|
120
|
+
*/
|
|
121
|
+
export async function checkHandshakeSolution(
|
|
122
|
+
keystone: KeystoneIbGib_V1,
|
|
123
|
+
solutionValue: string
|
|
124
|
+
): Promise<boolean> {
|
|
125
|
+
const handshakePool = (keystone.data?.challengePools ?? []).find(
|
|
126
|
+
(p) => p.id === SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID
|
|
127
|
+
);
|
|
128
|
+
if (!handshakePool) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { challengeId, challenge } = getHandshakeChallenge(keystone);
|
|
133
|
+
const strategy = KeystoneStrategyFactory.create({ config: handshakePool.config });
|
|
134
|
+
const solution: KeystoneSolution = {
|
|
135
|
+
type: 'hash-reveal-v1',
|
|
136
|
+
challengeId,
|
|
137
|
+
poolId: SESSION_KEYSTONE_POLICY.HANDSHAKE_POOL.ID,
|
|
138
|
+
value: solutionValue
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return await strategy.validateSolution({ solution, challenge });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// this is what we had by default inside the sync saga coordinator
|
|
145
|
+
// const primaryPoolConfig: KeystonePoolConfig_HashV1 = {
|
|
146
|
+
// allowedVerbs: [KEYSTONE_VERB_MANAGE],
|
|
147
|
+
// id: SESSION_IDENTITY_KEYSTONE_PRIMARY_POOL_ID,
|
|
148
|
+
// salt: sagaId,
|
|
149
|
+
// behavior: {
|
|
150
|
+
// size: SESSION_IDENTITY_KEYSTONE_CONFIG_SIZE, // Large pool for many signatures
|
|
151
|
+
// replenish: SESSION_IDENTITY_KEYSTONE_CONFIG_REPLENISH_STRATEGY,
|
|
152
|
+
// selectSequentially: SESSION_IDENTITY_KEYSTONE_CONFIG_SEQUENTIAL,
|
|
153
|
+
// selectRandomly: SESSION_IDENTITY_KEYSTONE_CONFIG_RANDOM,
|
|
154
|
+
// targetBindingChars: SESSION_IDENTITY_KEYSTONE_CONFIG_TARGET_BINDING,
|
|
155
|
+
// },
|
|
156
|
+
// type: KeystoneChallengeType.hash_reveal_v1,
|
|
157
|
+
// algo: SESSION_IDENTITY_KEYSTONE_CONFIG_ALGO,
|
|
158
|
+
// rounds: SESSION_IDENTITY_KEYSTONE_CONFIG_ROUNDS,
|
|
159
|
+
// };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Space-Gib Server
|
|
2
|
+
|
|
3
|
+
This directory contains the Node.js server implementation for `space-gib`.
|
|
4
|
+
|
|
5
|
+
The server is built on a custom microframework called `serve-gib`. For details on how routing, contexts, and request handling work within the framework, please refer to the [serve-gib Microframework Guide](serve-gib/README.md).
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
* **Framework**: Uses the minimalist `serve-gib` microframework instead of Express to maintain strict control over routing and IbGib multitenant contexts.
|
|
10
|
+
* **Hosting**: The server runs inside a Docker container orchestrated via `docker-compose`.
|
|
11
|
+
* **Build**: Compiled and bundled by `esbuild` using the monorepo's shared `build-space-gib.mts` script.
|
|
12
|
+
|
|
13
|
+
## Core Files
|
|
14
|
+
* `server.mts`: The main entry point that initializes the server, handles WebSocket upgrades, and defines the HTTP routing pipeline.
|
|
15
|
+
* `path-constants.mts`: Canonical definitions for all API regex routes.
|
|
16
|
+
* `bootstrap-helper.mts`: Logic for initializing domain-isolated multitenant metaspaces.
|
|
17
|
+
|
|
18
|
+
For local development commands, please refer to the [Root space-gib README](../../README.md).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { clone, getUUID, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
4
|
+
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
5
|
+
import { getGib, getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
|
|
6
|
+
import {
|
|
7
|
+
MetaspaceFactory, MetaspaceService, ZeroSpaceFactoryFunction,
|
|
8
|
+
LocalSpaceFactoryFunction, DtoToSpaceFunction
|
|
9
|
+
} from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs';
|
|
10
|
+
import { NodeIndexedFilesystemSpace_V1 } from '@ibgib/node-gib/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/node-indexed-filesystem-space-v1.mjs';
|
|
11
|
+
import {
|
|
12
|
+
NodeFilesystemSpaceData_V1, DEFAULT_NODE_FILESYSTEM_SPACE_DATA_V1,
|
|
13
|
+
} from '@ibgib/node-gib/dist/witness/space/node-filesystem-space/node-filesystem-space-types.mjs';
|
|
14
|
+
|
|
15
|
+
import { GLOBAL_LOG_A_LOT } from './server-constants.mjs';
|
|
16
|
+
import { NodeIndexedFilesystemSpaceData_V1, NodeIndexedFilesystemSpaceRel8ns_V1 } from '@ibgib/node-gib/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/node-indexed-filesystem-space-types.mjs';
|
|
17
|
+
import { Metaspace_Nodeindexedspace } from './metaspace-nodeindexedspace/metaspace-nodeindexedspace.mjs';
|
|
18
|
+
|
|
19
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Deterministically calculates the physical filesystem path for a domain
|
|
23
|
+
* based on its keystone's tjpGib.
|
|
24
|
+
*
|
|
25
|
+
* @param tjpGib The TJP hash of the keystone defining the domain.
|
|
26
|
+
* @param baseDataDir The root directory for all ibgib spaces on the server.
|
|
27
|
+
* @returns The absolute path to the domain's isolated root.
|
|
28
|
+
*/
|
|
29
|
+
export function getDomainRootPath(tjpGib: string, baseDataDir: string): string {
|
|
30
|
+
// We use the first 2 characters as subfolders to balance the OS filesystem entries.
|
|
31
|
+
// e.g. /data/spaces/ab/c1/defg...
|
|
32
|
+
const sub1 = tjpGib.slice(0, 2);
|
|
33
|
+
const sub2 = tjpGib.slice(2, 4);
|
|
34
|
+
const remaining = tjpGib;
|
|
35
|
+
|
|
36
|
+
return join(baseDataDir, 'domains', sub1, sub2, remaining);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a MetaspaceFactory using the "Factory Closure Pattern" to isolate
|
|
41
|
+
* all space operations within the given domainRootPath.
|
|
42
|
+
*
|
|
43
|
+
* @param domainRootPath The physical root directory for this domain.
|
|
44
|
+
* @returns A MetaspaceFactory pre-configured for this domain.
|
|
45
|
+
*/
|
|
46
|
+
export function getDomainFactory(domainRootPath: string): MetaspaceFactory {
|
|
47
|
+
const lc = `[${getDomainFactory.name}]`;
|
|
48
|
+
|
|
49
|
+
const fnZeroSpaceFactory: ZeroSpaceFactoryFunction = () => {
|
|
50
|
+
const zeroSpaceData: NodeIndexedFilesystemSpaceData_V1 = {
|
|
51
|
+
...clone(DEFAULT_NODE_FILESYSTEM_SPACE_DATA_V1),
|
|
52
|
+
classname: NodeIndexedFilesystemSpace_V1.name,
|
|
53
|
+
baseDir: domainRootPath,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const zeroSpace = new NodeIndexedFilesystemSpace_V1(zeroSpaceData);
|
|
57
|
+
zeroSpace.gib = 'gib'; // Primitive zero space
|
|
58
|
+
return zeroSpace;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const fnDefaultLocalSpaceFactory: LocalSpaceFactoryFunction = async (opts) => {
|
|
62
|
+
// Ensure we don't prompt for a name during server bootstrap
|
|
63
|
+
// opts.spaceName ||= `default_${pickRandom_Letters({ count: 5 })}`;
|
|
64
|
+
opts.spaceName ||= `default`;
|
|
65
|
+
|
|
66
|
+
const spaceData: NodeFilesystemSpaceData_V1 = {
|
|
67
|
+
...clone(DEFAULT_NODE_FILESYSTEM_SPACE_DATA_V1),
|
|
68
|
+
classname: NodeIndexedFilesystemSpace_V1.name,
|
|
69
|
+
uuid: await getUUID(),
|
|
70
|
+
baseDir: domainRootPath,
|
|
71
|
+
name: opts.spaceName,
|
|
72
|
+
spaceSubPath: opts.spaceName,
|
|
73
|
+
baseSubPath: 'ibgib',
|
|
74
|
+
binSubPath: 'bin',
|
|
75
|
+
dnaSubPath: 'dna',
|
|
76
|
+
ibgibsSubPath: 'ibgibs',
|
|
77
|
+
metaSubPath: 'meta',
|
|
78
|
+
isTjp: true,
|
|
79
|
+
};
|
|
80
|
+
if (logalot) { console.log(`${lc}[${fnDefaultLocalSpaceFactory.name}] spaceData.classname: ${spaceData.classname} (I: b60da661e9282e1253101e08765eb226)`); }
|
|
81
|
+
|
|
82
|
+
const space = new NodeIndexedFilesystemSpace_V1(spaceData);
|
|
83
|
+
space.gib = await getGib({ ibGib: space as any, hasTjp: true });
|
|
84
|
+
|
|
85
|
+
return space;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const fnDtoToSpace: DtoToSpaceFunction = async (spaceDto: IbGib_V1) => {
|
|
89
|
+
if (!spaceDto) { throw new Error(`spaceDto required (E: 30ede52f9c974591dac980a810510e26)`); }
|
|
90
|
+
if (!spaceDto.data) { throw new Error(`spaceDto.data required (E: 867b08a9c998e95028396f283d1d4826)`); }
|
|
91
|
+
if (!spaceDto.data.classname) { throw new Error(`spaceDto.data.classname required (E: 95a68813e64e68e6c84296d8dbaeb826)`); }
|
|
92
|
+
if (spaceDto.data.classname !== NodeIndexedFilesystemSpace_V1.name) {
|
|
93
|
+
throw new Error(`spaceDto.data.classname (${spaceDto.data.classname}) expeced to be ${NodeIndexedFilesystemSpace_V1.name} (E: 68aaa875ff22b92f57a0a3189da4b826)`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const space = await NodeIndexedFilesystemSpace_V1.createFromDto(
|
|
97
|
+
spaceDto as IbGib_V1<NodeIndexedFilesystemSpaceData_V1, NodeIndexedFilesystemSpaceRel8ns_V1>
|
|
98
|
+
);
|
|
99
|
+
return space;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
fnZeroSpaceFactory,
|
|
105
|
+
fnDefaultLocalSpaceFactory,
|
|
106
|
+
fnDtoToSpace,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Orchestrates the bootstrapping of a domain-isolated metaspace.
|
|
112
|
+
*
|
|
113
|
+
* @param keystoneAddr The full address of the keystone defining the domain.
|
|
114
|
+
* @param baseDataDir The server's root data directory.
|
|
115
|
+
* @returns An initialized MetaspaceService scoped to the domain.
|
|
116
|
+
*/
|
|
117
|
+
export async function bootstrapDomainMetaspace(
|
|
118
|
+
keystoneAddr: string,
|
|
119
|
+
baseDataDir: string
|
|
120
|
+
): Promise<MetaspaceService> {
|
|
121
|
+
const { tjpGib, punctiliarHash } = getGibInfo({ ibGibAddr: keystoneAddr });
|
|
122
|
+
const domainGib = tjpGib || punctiliarHash;
|
|
123
|
+
if (!domainGib) {
|
|
124
|
+
throw new Error(`Could not extract tjpGib from keystone address: ${keystoneAddr}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const domainRootPath = getDomainRootPath(domainGib, baseDataDir);
|
|
128
|
+
const factory = getDomainFactory(domainRootPath);
|
|
129
|
+
|
|
130
|
+
const metaspace = new Metaspace_Nodeindexedspace(/*cacheSvc*/ undefined);
|
|
131
|
+
|
|
132
|
+
await metaspace.initialize({
|
|
133
|
+
metaspaceFactory: factory,
|
|
134
|
+
// For server-side, these prompts should probably be non-interactive or log errors.
|
|
135
|
+
getFnAlert: () => async (arg) => console.log(`[ALERT] ${arg.title}: ${arg.msg}`),
|
|
136
|
+
getFnPrompt: () => async (arg) => { throw new Error(`Interactive prompt (${arg.title}) not supported in server bootstrap.`); },
|
|
137
|
+
getFnPromptPassword: () => async (title) => { throw new Error(`Password prompt (${title}) not supported in server bootstrap.`); },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return metaspace;
|
|
141
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import { respecfully, iReckon, ifWe } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
5
|
+
const sir = `[${import.meta.url}]`;
|
|
6
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
7
|
+
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
8
|
+
import { createStandardPoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-config-builder.mjs';
|
|
9
|
+
import { POOL_ID_DEFAULT } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
10
|
+
|
|
11
|
+
import { GLOBAL_LOG_A_LOT } from './server-constants.mjs';
|
|
12
|
+
import { getDomainRootPath, bootstrapDomainMetaspace } from './bootstrap-helper.mjs';
|
|
13
|
+
|
|
14
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Minimal mock to satisfy KeystoneService requirements during genesis.
|
|
19
|
+
*/
|
|
20
|
+
class MockMetaspace {
|
|
21
|
+
async getLocalUserSpace() { return new MockSpace(); }
|
|
22
|
+
async put() { }
|
|
23
|
+
async registerNewIbGib() { }
|
|
24
|
+
}
|
|
25
|
+
class MockSpace {
|
|
26
|
+
async witness() { return { data: { success: true } }; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await respecfully(sir, 'bootstrap-helper', async () => {
|
|
30
|
+
const lc = `[bootstrap-helper.respec.mts][bootstrap-helper]`;
|
|
31
|
+
const testDataDir = './test-data';
|
|
32
|
+
console.log(`${lc} testDataDir: ${testDataDir} (I: c9f39853a57a5af97ab8ff7882c49826)`);
|
|
33
|
+
|
|
34
|
+
// Clean and recreate test-data directory
|
|
35
|
+
if (existsSync(testDataDir)) {
|
|
36
|
+
rmSync(testDataDir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
mkdirSync(testDataDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
await ifWe(sir, 'calculate balanced domain root paths', async () => {
|
|
41
|
+
const tjpGib = 'abcde1234567890';
|
|
42
|
+
const root = getDomainRootPath(tjpGib, testDataDir);
|
|
43
|
+
|
|
44
|
+
// Should be: testDataDir/domains/ab/cd/abcde1234567890
|
|
45
|
+
iReckon(sir, root).includes(join('domains', 'ab', 'cd', tjpGib));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await ifWe(sir, 'bootstrap a domain metaspace and create the directory', async () => {
|
|
49
|
+
const keystoneAddr = 'keystone_alice^abcde1234567890';
|
|
50
|
+
const metaspace = await bootstrapDomainMetaspace(keystoneAddr, testDataDir);
|
|
51
|
+
|
|
52
|
+
iReckon(sir, metaspace).isGonnaBe(metaspace);
|
|
53
|
+
iReckon(sir, metaspace.initialized).isGonnaBe(true);
|
|
54
|
+
|
|
55
|
+
const expectedPath = getDomainRootPath('abcde1234567890', testDataDir);
|
|
56
|
+
iReckon(sir, existsSync(expectedPath)).isGonnaBe(true);
|
|
57
|
+
|
|
58
|
+
// The ibgib framework uses a specific subdirectory structure for the bootstrap
|
|
59
|
+
const bootstrapFile = join(expectedPath, 'ibgib', '000_zerospace', 'gib', 'bootstrap.json');
|
|
60
|
+
iReckon(sir, existsSync(bootstrapFile)).isGonnaBe(true);
|
|
61
|
+
|
|
62
|
+
// Also check that the default space was created
|
|
63
|
+
const defaultSpaceDir = join(expectedPath, 'ibgib', 'default');
|
|
64
|
+
iReckon(sir, existsSync(defaultSpaceDir)).isGonnaBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await ifWe(sir, 'integrate with KeystoneService and bootstrap 5 unique domains', async () => {
|
|
68
|
+
const keystoneService = new KeystoneService_V1();
|
|
69
|
+
const mockMetaspace = new MockMetaspace() as any;
|
|
70
|
+
const mockSpace = new MockSpace() as any;
|
|
71
|
+
const config = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: POOL_ID_DEFAULT });
|
|
72
|
+
|
|
73
|
+
const identities = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'];
|
|
74
|
+
|
|
75
|
+
for (const name of identities) {
|
|
76
|
+
// 1. Generate an actual genesis keystone for this identity
|
|
77
|
+
const keystone = await keystoneService.genesis({
|
|
78
|
+
masterSecret: `SecretFor_${name}`,
|
|
79
|
+
configs: [config],
|
|
80
|
+
metaspace: mockMetaspace,
|
|
81
|
+
space: mockSpace,
|
|
82
|
+
});
|
|
83
|
+
const addr = getIbGibAddr({ ibGib: keystone });
|
|
84
|
+
const tjpGib = keystone.gib!;
|
|
85
|
+
|
|
86
|
+
if (logalot) { console.log(`[test] Bootstrapping domain for ${name}: ${addr} (I: 7ba45828f2719ca578a1f078ff16e126)`); }
|
|
87
|
+
|
|
88
|
+
// 2. Drive the bootstrap logic
|
|
89
|
+
const metaspace = await bootstrapDomainMetaspace(addr, testDataDir);
|
|
90
|
+
iReckon(sir, metaspace.initialized).isGonnaBe(true);
|
|
91
|
+
|
|
92
|
+
// 3. Verify the unique path
|
|
93
|
+
const expectedPath = getDomainRootPath(tjpGib, testDataDir);
|
|
94
|
+
iReckon(sir, existsSync(expectedPath)).isGonnaBe(true);
|
|
95
|
+
|
|
96
|
+
const bootstrapFile = join(expectedPath, 'ibgib', '000_zerospace', 'gib', 'bootstrap.json');
|
|
97
|
+
iReckon(sir, existsSync(bootstrapFile)).isGonnaBe(true);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-metaspace driven by a node+fs storage substrate
|
|
3
|
+
*
|
|
4
|
+
* a metaspace is a space of spaces. it adds/removes spaces and does
|
|
5
|
+
* inter-spatial composition related things. it's similar to what people think
|
|
6
|
+
* of in terms of a node in a p2p network.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
10
|
+
import { IbGibSpaceAny } from '@ibgib/core-gib/dist/witness/space/space-base-v1.mjs';
|
|
11
|
+
import { IbGibCacheService } from '@ibgib/core-gib/dist/common/cache/cache-types.mjs';
|
|
12
|
+
import { MetaspaceBase } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-base.mjs';
|
|
13
|
+
import { MetaspaceFactory } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs';
|
|
14
|
+
|
|
15
|
+
import { GLOBAL_LOG_A_LOT, GLOBAL_TIMER_NAME } from '../server-constants.mjs';
|
|
16
|
+
|
|
17
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
export class Metaspace_Nodeindexedspace extends MetaspaceBase {
|
|
23
|
+
|
|
24
|
+
// we won't get an object back, only a DTO ibGib essentially
|
|
25
|
+
protected lc: string = `[${Metaspace_Nodeindexedspace.name}]`;
|
|
26
|
+
|
|
27
|
+
get zeroSpace(): IbGibSpaceAny {
|
|
28
|
+
const lc = `[${this.lc}][get zeroSpace]`;
|
|
29
|
+
if (this.metaspaceFactory.fnZeroSpaceFactory) {
|
|
30
|
+
return this.metaspaceFactory.fnZeroSpaceFactory();
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error(`${lc} (UNEXPECTED) this.metaspaceFactory.fnZeroSpaceFactory falsy. not initialized? (E: 9e6c6954dacd868b18c9f34a71e63523)`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
// public modalController: ModalController,
|
|
38
|
+
// public alertController: AlertController,
|
|
39
|
+
protected cacheSvc: IbGibCacheService | undefined,
|
|
40
|
+
// private latestCacheSvc: IonicStorageLatestIbgibCacheService,
|
|
41
|
+
) {
|
|
42
|
+
super(cacheSvc);
|
|
43
|
+
const lc = `${this.lc}[ctor]`;
|
|
44
|
+
if (logalot) {
|
|
45
|
+
console.log(`${lc}${GLOBAL_TIMER_NAME}`);
|
|
46
|
+
console.timeLog(GLOBAL_TIMER_NAME);
|
|
47
|
+
console.log(`${lc} created. (I: 5690dc2ebf774df3a442bd463dee7455)`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected async initializeMetaspaceFactory({ metaspaceFactory }: {
|
|
53
|
+
metaspaceFactory: MetaspaceFactory,
|
|
54
|
+
}): Promise<void> {
|
|
55
|
+
const lc = `[${this.initializeMetaspaceFactory.name}]`;
|
|
56
|
+
try {
|
|
57
|
+
if (logalot) { console.log(`${lc} starting... (I: 5a29670439ac47634dcdfe25225c8223)`); }
|
|
58
|
+
if (!metaspaceFactory) { throw new Error(`(UNEXPECTED) metaspaceFactory falsy? space-gib server initialization should have guaranteed this to be truthy (E: f305356c2978cf0ee8062fd818940826)`); }
|
|
59
|
+
if (!metaspaceFactory.fnDefaultLocalSpaceFactory) { throw new Error(`(UNEXPECTED) !metaspaceFactory.fnDefaultLocalSpaceFactory? space-gib server initialization should have guaranteed this to be truthy (E: 884281e5251bd65528eb7b3f48737826)`); }
|
|
60
|
+
if (!metaspaceFactory.fnDtoToSpace) { throw new Error(`(UNEXPECTED) !metaspaceFactory.fnDtoToSpace? space-gib server initialization should have guaranteed this to be truthy (E: 885b7828606f36f9183bb9e687e25826)`); }
|
|
61
|
+
if (!metaspaceFactory.fnZeroSpaceFactory) { throw new Error(`(UNEXPECTED) !metaspaceFactory.fnZeroSpaceFactory? space-gib server initialization should have guaranteed this to be truthy (E: 336cf6655758bad4aaeea8ce08d8c826)`); }
|
|
62
|
+
|
|
63
|
+
await super.initializeMetaspaceFactory({ metaspaceFactory });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
66
|
+
throw error;
|
|
67
|
+
} finally {
|
|
68
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// async initializeZeroSpace(): Promise<void> {
|
|
73
|
+
// const lc = `[${this.initializeZeroSpace.name}]`;
|
|
74
|
+
// try {
|
|
75
|
+
// if (logalot) { console.log(`${lc} starting... (I: a3364e9601c70c8163e67868e5d67723)`); }
|
|
76
|
+
|
|
77
|
+
// } catch (error) {
|
|
78
|
+
// console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
79
|
+
// throw error;
|
|
80
|
+
// } finally {
|
|
81
|
+
// if (logalot) { console.log(`${lc} complete.`); }
|
|
82
|
+
// }
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Maximum allowed path length to prevent DoS. */
|
|
2
|
+
export const MAX_PATH_LENGTH = 2048;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Blacklisted patterns for any incoming path.
|
|
6
|
+
* We iterate through these for a quick security check.
|
|
7
|
+
*/
|
|
8
|
+
export const FORBIDDEN_PATTERNS = [
|
|
9
|
+
/\.\./, // No directory traversal
|
|
10
|
+
/\/\//, // No redundant slashes
|
|
11
|
+
/\0/, // No null bytes
|
|
12
|
+
/(^|\/)\./, // No hidden/dot-files (e.g. .env, .git)
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Regex for valid API paths.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT: See IMPLEMENTATION.md for overall path schema documentation.
|
|
19
|
+
*
|
|
20
|
+
* Paths are URL encoded. The `ib` and `gib` portions of addresses are captured explicitly
|
|
21
|
+
* by splitting on `%5E` or `^`.
|
|
22
|
+
*/
|
|
23
|
+
export const API_PATH_REGEXES = {
|
|
24
|
+
/**
|
|
25
|
+
* /api/health
|
|
26
|
+
*/
|
|
27
|
+
HEALTH: /^\/api\/health\/?$/,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* /api/ibgib/graph/:domainAddr/:ibgibAddr
|
|
31
|
+
*/
|
|
32
|
+
IBGIB_GRAPH: /^\/api\/ibgib\/graph\/([^\/]+?)(?:%5E|\^)([^\/]+)\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* /api/ibgib/:domainAddr/:ibgibAddr
|
|
36
|
+
*/
|
|
37
|
+
IBGIB_ADDR: /^\/api\/ibgib\/([^\/]+?)(?:%5E|\^)([^\/]+)\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* /api/keystone
|
|
41
|
+
*/
|
|
42
|
+
KEYSTONE: /^\/api\/keystone\/?$/,
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* /api/keystone/:domainAddr
|
|
46
|
+
*/
|
|
47
|
+
KEYSTONE_ADDR: /^\/api\/keystone\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* /api/ibgib
|
|
51
|
+
*/
|
|
52
|
+
IBGIB_BASE: /^\/api\/ibgib\/?$/,
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* /api/keystone/evolve/:domainAddr
|
|
56
|
+
*/
|
|
57
|
+
KEYSTONE_EVOLVE: /^\/api\/keystone\/evolve\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* /api/keystone/genesis
|
|
61
|
+
*
|
|
62
|
+
* Accepts a single genesis-frame keystone ibgib and creates a new
|
|
63
|
+
* domain-isolated metaspace for it. No `:domainAddr` in the URL because
|
|
64
|
+
* the server derives the domain from the incoming keystone ibgib itself.
|
|
65
|
+
*
|
|
66
|
+
* Parallel to /api/keystone/evolve.
|
|
67
|
+
*/
|
|
68
|
+
KEYSTONE_GENESIS: /^\/api\/keystone\/genesis\/?$/,
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* /api/debug/ws-echo
|
|
72
|
+
*
|
|
73
|
+
* Minimal WebSocket echo endpoint for validating WS plumbing (Traefik
|
|
74
|
+
* passthrough, server upgrade handling, client connectivity) before
|
|
75
|
+
* integrating real sync logic.
|
|
76
|
+
*
|
|
77
|
+
* ## intent
|
|
78
|
+
*
|
|
79
|
+
* NOT for production use. Remove or gate behind a flag once WS sync is
|
|
80
|
+
* confirmed working end-to-end.
|
|
81
|
+
*/
|
|
82
|
+
DEBUG_WS_ECHO: /^\/api\/debug\/ws-echo\/?$/,
|
|
83
|
+
/**
|
|
84
|
+
* /api/sync/ws/:domainAddr
|
|
85
|
+
*/
|
|
86
|
+
SYNC_WS: /^\/api\/sync\/ws\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const VALID_STATIC_PATH_REGEX = /^\/[a-zA-Z0-9\-_\.\/]*$/;
|