@ibgib/node-gib 0.0.14 → 0.0.17
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/data/identity_index/email/hashes/32/13/49/e64f32730f01a61ada211f29eaf0a210fc2386b668ad04d7e8a9b16766.json +4 -0
- package/dist/email/email-helpers.d.mts.map +1 -1
- package/dist/email/email-helpers.mjs.map +1 -1
- package/dist/email/index.d.mts +3 -0
- package/dist/email/index.d.mts.map +1 -1
- package/dist/email/index.mjs +3 -0
- package/dist/email/index.mjs.map +1 -1
- package/dist/email/verify/email-verify-constants.d.mts +3 -0
- package/dist/email/verify/email-verify-constants.d.mts.map +1 -0
- package/dist/email/verify/email-verify-constants.mjs +3 -0
- package/dist/email/verify/email-verify-constants.mjs.map +1 -0
- package/dist/email/verify/email-verify-helpers.d.mts +107 -0
- package/dist/email/verify/email-verify-helpers.d.mts.map +1 -0
- package/dist/email/verify/email-verify-helpers.mjs +357 -0
- package/dist/email/verify/email-verify-helpers.mjs.map +1 -0
- package/dist/email/verify/email-verify-types.d.mts +41 -0
- package/dist/email/verify/email-verify-types.d.mts.map +1 -0
- package/dist/email/verify/email-verify-types.mjs +2 -0
- package/dist/email/verify/email-verify-types.mjs.map +1 -0
- package/dist/identity/sso/custodian-auth-helpers.d.mts +45 -0
- package/dist/identity/sso/custodian-auth-helpers.d.mts.map +1 -0
- package/dist/identity/sso/custodian-auth-helpers.mjs +108 -0
- package/dist/identity/sso/custodian-auth-helpers.mjs.map +1 -0
- package/dist/identity/sso/sso-config-helper.d.mts +7 -0
- package/dist/identity/sso/sso-config-helper.d.mts.map +1 -0
- package/dist/identity/sso/sso-config-helper.mjs +42 -0
- package/dist/identity/sso/sso-config-helper.mjs.map +1 -0
- package/dist/identity/sso/sso-custodian-service.d.mts +139 -0
- package/dist/identity/sso/sso-custodian-service.d.mts.map +1 -0
- package/dist/identity/sso/sso-custodian-service.mjs +471 -0
- package/dist/identity/sso/sso-custodian-service.mjs.map +1 -0
- package/dist/identity/sso/sso-types.d.mts +71 -0
- package/dist/identity/sso/sso-types.d.mts.map +1 -0
- package/dist/identity/sso/sso-types.mjs +7 -0
- package/dist/identity/sso/sso-types.mjs.map +1 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/email/email-outbound.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/email/email-outbound.handler.mjs +9 -2
- package/dist/serve-gib/handlers/api/email/email-outbound.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/email/email-verification-code.handler.d.mts +29 -0
- package/dist/serve-gib/handlers/api/email/email-verification-code.handler.d.mts.map +1 -0
- package/dist/serve-gib/handlers/api/email/email-verification-code.handler.mjs +149 -0
- package/dist/serve-gib/handlers/api/email/email-verification-code.handler.mjs.map +1 -0
- package/dist/serve-gib/handlers/api/email/email-verify-code-onetime.handler.d.mts +24 -0
- package/dist/serve-gib/handlers/api/email/email-verify-code-onetime.handler.d.mts.map +1 -0
- package/dist/serve-gib/handlers/api/email/email-verify-code-onetime.handler.mjs +212 -0
- package/dist/serve-gib/handlers/api/email/email-verify-code-onetime.handler.mjs.map +1 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.d.mts +37 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.d.mts.map +1 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.mjs +192 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.mjs.map +1 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.d.mts +33 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.d.mts.map +1 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mjs +173 -0
- package/dist/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mjs.map +1 -0
- package/dist/serve-gib/handlers/api/keystone/keystone-check-email.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-check-email.handler.mjs +2 -3
- package/dist/serve-gib/handlers/api/keystone/keystone-check-email.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-evolve.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-evolve.handler.mjs +38 -3
- package/dist/serve-gib/handlers/api/keystone/keystone-evolve.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-genesis.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-genesis.handler.mjs +54 -24
- package/dist/serve-gib/handlers/api/keystone/keystone-genesis.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-lookup.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-lookup.handler.mjs +13 -13
- package/dist/serve-gib/handlers/api/keystone/keystone-lookup.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-post.handler.mjs +1 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-post.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-config.handler.mjs +3 -3
- package/dist/serve-gib/handlers/api/keystone/sso-config.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-link.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-link.handler.mjs +28 -15
- package/dist/serve-gib/handlers/api/keystone/sso-link.handler.mjs.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.d.mts +29 -15
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.d.mts.map +1 -1
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.mjs +105 -108
- package/dist/serve-gib/handlers/api/keystone/sso-login.handler.mjs.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-constants.d.mts +3 -2
- package/dist/serve-gib/identity-index/identity-index-constants.d.mts.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-constants.mjs +4 -2
- package/dist/serve-gib/identity-index/identity-index-constants.mjs.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-helpers.d.mts.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-helpers.mjs +28 -11
- package/dist/serve-gib/identity-index/identity-index-helpers.mjs.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-service.d.mts +20 -3
- package/dist/serve-gib/identity-index/identity-index-service.d.mts.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-service.mjs +134 -18
- package/dist/serve-gib/identity-index/identity-index-service.mjs.map +1 -1
- package/dist/serve-gib/identity-index/identity-index-types.d.mts +4 -2
- package/dist/serve-gib/identity-index/identity-index-types.d.mts.map +1 -1
- package/dist/serve-gib/index.d.mts +4 -0
- package/dist/serve-gib/index.d.mts.map +1 -1
- package/dist/serve-gib/index.mjs +12 -0
- package/dist/serve-gib/index.mjs.map +1 -1
- package/dist/serve-gib/path-constants.d.mts +17 -0
- package/dist/serve-gib/path-constants.d.mts.map +1 -1
- package/dist/serve-gib/path-constants.mjs +17 -0
- package/dist/serve-gib/path-constants.mjs.map +1 -1
- package/e2e/identity-custodian-lifecycle.e2e.md +93 -0
- package/e2e/identity-sso-lifecycle.e2e.md +132 -0
- package/package.json +5 -5
- package/src/email/email-helpers.mts +0 -1
- package/src/email/index.mts +3 -0
- package/src/email/verify/email-verify-constants.mts +3 -0
- package/src/email/verify/email-verify-helpers.mts +466 -0
- package/src/email/verify/email-verify-types.mts +47 -0
- package/src/identity/sso/custodian-auth-helpers.mts +146 -0
- package/src/identity/sso/custodian-auth-helpers.respec.mts +97 -0
- package/src/identity/sso/sso-config-helper.mts +52 -0
- package/src/identity/sso/sso-custodian-service.mts +566 -0
- package/src/identity/sso/sso-custodian-service.respec.mts +297 -0
- package/src/identity/sso/sso-types.mts +80 -0
- package/src/index.mts +14 -0
- package/src/serve-gib/handlers/api/email/email-outbound.handler.mts +8 -2
- package/src/serve-gib/handlers/api/email/email-verification-code.handler.mts +163 -0
- package/src/serve-gib/handlers/api/email/email-verify-code-onetime.handler.mts +231 -0
- package/src/serve-gib/handlers/api/email/email-verify.respec.mts +183 -0
- package/src/serve-gib/handlers/api/identity/keystone-custodian-genesis.handler.mts +233 -0
- package/src/serve-gib/handlers/api/identity/keystone-custodian-genesis.respec.mts +93 -0
- package/src/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.handler.mts +210 -0
- package/src/serve-gib/handlers/api/identity/keystone-custodian-register-delegate.respec.mts +112 -0
- package/src/serve-gib/handlers/api/keystone/keystone-check-email.handler.mts +2 -4
- package/src/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +42 -3
- package/src/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +46 -23
- package/src/serve-gib/handlers/api/keystone/keystone-lookup.handler.mts +11 -15
- package/src/serve-gib/handlers/api/keystone/keystone-post.handler.mts +1 -1
- package/src/serve-gib/handlers/api/keystone/sso-config.handler.mts +3 -3
- package/src/serve-gib/handlers/api/keystone/sso-link.handler.mts +29 -17
- package/src/serve-gib/handlers/api/keystone/sso-login.handler.mts +132 -125
- package/src/serve-gib/identity-index/identity-index-constants.mts +5 -2
- package/src/serve-gib/identity-index/identity-index-helpers.mts +26 -12
- package/src/serve-gib/identity-index/identity-index-service.mts +133 -16
- package/src/serve-gib/identity-index/identity-index-service.respec.mts +1 -1
- package/src/serve-gib/identity-index/identity-index-types.mts +4 -2
- package/src/serve-gib/index.mts +12 -0
- package/src/serve-gib/path-constants.mts +20 -0
- package/tsconfig.json +2 -2
- package/tsconfig.test.json +1 -1
- package/dist/assumptions.respec.d.mts +0 -2
- package/dist/assumptions.respec.d.mts.map +0 -1
- package/dist/assumptions.respec.mjs +0 -41
- package/dist/assumptions.respec.mjs.map +0 -1
- package/dist/email/email-service.respec.d.mts +0 -2
- package/dist/email/email-service.respec.d.mts.map +0 -1
- package/dist/email/email-service.respec.mjs +0 -115
- package/dist/email/email-service.respec.mjs.map +0 -1
- package/dist/serve-gib/bootstrap-helper.respec.d.mts +0 -2
- package/dist/serve-gib/bootstrap-helper.respec.d.mts.map +0 -1
- package/dist/serve-gib/bootstrap-helper.respec.mjs +0 -85
- package/dist/serve-gib/bootstrap-helper.respec.mjs.map +0 -1
- package/dist/serve-gib/handlers/api/health.respec.d.mts +0 -2
- package/dist/serve-gib/handlers/api/health.respec.d.mts.map +0 -1
- package/dist/serve-gib/handlers/api/health.respec.mjs +0 -47
- package/dist/serve-gib/handlers/api/health.respec.mjs.map +0 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-get.respec.d.mts +0 -2
- package/dist/serve-gib/handlers/api/keystone/keystone-get.respec.d.mts.map +0 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-get.respec.mjs +0 -98
- package/dist/serve-gib/handlers/api/keystone/keystone-get.respec.mjs.map +0 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-post.respec.d.mts +0 -2
- package/dist/serve-gib/handlers/api/keystone/keystone-post.respec.d.mts.map +0 -1
- package/dist/serve-gib/handlers/api/keystone/keystone-post.respec.mjs +0 -131
- package/dist/serve-gib/handlers/api/keystone/keystone-post.respec.mjs.map +0 -1
- package/dist/serve-gib/identity-index/identity-index-service.respec.d.mts +0 -2
- package/dist/serve-gib/identity-index/identity-index-service.respec.d.mts.map +0 -1
- package/dist/serve-gib/identity-index/identity-index-service.respec.mjs +0 -109
- package/dist/serve-gib/identity-index/identity-index-service.respec.mjs.map +0 -1
- package/dist/serve-gib/keystone-schema-helpers.respec.d.mts +0 -2
- package/dist/serve-gib/keystone-schema-helpers.respec.d.mts.map +0 -1
- package/dist/serve-gib/keystone-schema-helpers.respec.mjs +0 -68
- package/dist/serve-gib/keystone-schema-helpers.respec.mjs.map +0 -1
- package/dist/serve-gib/path-helper.respec.d.mts +0 -2
- package/dist/serve-gib/path-helper.respec.d.mts.map +0 -1
- package/dist/serve-gib/path-helper.respec.mjs +0 -83
- package/dist/serve-gib/path-helper.respec.mjs.map +0 -1
- package/dist/serve-gib/serve-gib.respec.d.mts +0 -2
- package/dist/serve-gib/serve-gib.respec.d.mts.map +0 -1
- package/dist/serve-gib/serve-gib.respec.mjs +0 -85
- package/dist/serve-gib/serve-gib.respec.mjs.map +0 -1
- package/dist/spec-helper.node.respec.d.mts +0 -13
- package/dist/spec-helper.node.respec.d.mts.map +0 -1
- package/dist/spec-helper.node.respec.mjs +0 -50
- package/dist/spec-helper.node.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_createAndInit.node-indexed-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_createAndInit.node-indexed-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_createAndInit.node-indexed-filesystem-space-v1.respec.mjs +0 -52
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_createAndInit.node-indexed-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_persistTransformResult.node-indexed-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_persistTransformResult.node-indexed-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_persistTransformResult.node-indexed-filesystem-space-v1.respec.mjs +0 -53
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_persistTransformResult.node-indexed-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_putGetDelete.node-indexed-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_putGetDelete.node-indexed-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_putGetDelete.node-indexed-filesystem-space-v1.respec.mjs +0 -59
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_putGetDelete.node-indexed-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-indexed-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-indexed-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-indexed-filesystem-space-v1.respec.mjs +0 -55
- package/dist/witness/space/node-filesystem-space/node-indexed-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-indexed-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.mjs +0 -67
- package/dist/witness/space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.mjs +0 -67
- package/dist/witness/space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.mjs +0 -68
- package/dist/witness/space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.mjs +0 -69
- package/dist/witness/space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.mjs.map +0 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/handlers/api/email/email-verify-code-onetime
|
|
3
|
+
*
|
|
4
|
+
* Handler for POST /api/email/verify-code-onetime (and alias /api/email/verify-code)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'node:fs/promises';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { extractErrorMsg, getUUID, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
12
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
13
|
+
import {
|
|
14
|
+
getPendingStagingDir,
|
|
15
|
+
getPendingTjpHash,
|
|
16
|
+
verifyPendingCode,
|
|
17
|
+
validateEmail
|
|
18
|
+
} from '../../../../email/index.mjs';
|
|
19
|
+
import { getIdentityIndexService } from '../../../identity-index/identity-index-service.mjs';
|
|
20
|
+
import { normalizeEmail } from '../../../identity-index/identity-index-helpers.mjs';
|
|
21
|
+
import { bootstrapDomainMetaspace } from '../../../bootstrap-helper.mjs';
|
|
22
|
+
import { API_PATH_REGEXES } from '../../../path-constants.mjs';
|
|
23
|
+
import { RequestContext, ResponseResult, ServeGibHttpMethod } from '../../../types.mjs';
|
|
24
|
+
import { ServeGibHandlerBase } from '../../handler-base.mjs';
|
|
25
|
+
import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
|
|
26
|
+
|
|
27
|
+
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
28
|
+
|
|
29
|
+
export interface EmailVerifyCodeRequestBody {
|
|
30
|
+
email: string;
|
|
31
|
+
primaryTjpAddr: string;
|
|
32
|
+
code: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* POST /api/email/verify-code-onetime
|
|
37
|
+
* Validates a 6-digit verification code, updates staging email verification state,
|
|
38
|
+
* promotes verified keystones to official space once 100% verified, and returns JSON.
|
|
39
|
+
*/
|
|
40
|
+
export class EmailVerifyCodeOnetimeHandler extends ServeGibHandlerBase {
|
|
41
|
+
protected override lc = `[${EmailVerifyCodeOnetimeHandler.name}]`;
|
|
42
|
+
protected override method: ServeGibHttpMethod = 'POST';
|
|
43
|
+
protected override regex = API_PATH_REGEXES.EMAIL_VERIFY_CODE || /^\/api\/email\/(?:verify-code-onetime|verify-code)\/?$/i;
|
|
44
|
+
|
|
45
|
+
protected async handleRouteImpl(reqCtx: RequestContext): Promise<ResponseResult | undefined> {
|
|
46
|
+
const lc = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
47
|
+
const timestampUUID = `${lc}[${(await getUUID()).substring(0, 8)}]`;
|
|
48
|
+
try {
|
|
49
|
+
if (logalot) { console.log(`${lc} starting... (I: e123456789abcdef0123456789a0001)`); }
|
|
50
|
+
if (logalot) { console.time(timestampUUID); }
|
|
51
|
+
|
|
52
|
+
if (reqCtx.method !== 'POST') {
|
|
53
|
+
return this.error(405, 'Method Not Allowed');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!reqCtx.body) {
|
|
57
|
+
return this.error(400, 'Request body is empty (E: 123456789abcdef0123456789a0001)');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let body: EmailVerifyCodeRequestBody;
|
|
61
|
+
try {
|
|
62
|
+
body = JSON.parse(reqCtx.body);
|
|
63
|
+
} catch {
|
|
64
|
+
return this.error(400, 'Request body must be valid JSON (E: 123456789abcdef0123456789a0002)');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (logalot) { console.timeLog(timestampUUID, 'parse complete'); }
|
|
68
|
+
|
|
69
|
+
const { email, primaryTjpAddr, code } = body;
|
|
70
|
+
|
|
71
|
+
if (!email || !validateEmail(email)) {
|
|
72
|
+
return this.error(400, 'Invalid email address format (E: 123456789abcdef0123456789a0003)');
|
|
73
|
+
}
|
|
74
|
+
if (!primaryTjpAddr || typeof primaryTjpAddr !== 'string') {
|
|
75
|
+
return this.error(400, 'primaryTjpAddr is required (E: 123456789abcdef0123456789a0004)');
|
|
76
|
+
}
|
|
77
|
+
if (!code || typeof code !== 'string' || code.trim().length !== 6) {
|
|
78
|
+
return this.error(400, 'Invalid 6-digit verification code format (E: 123456789abcdef0123456789a0005)');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const normEmail = normalizeEmail(email);
|
|
82
|
+
const tjpHash = await getPendingTjpHash(primaryTjpAddr);
|
|
83
|
+
|
|
84
|
+
if (!tjpHash || !reqCtx.dataDir) {
|
|
85
|
+
return this.error(400, 'Could not determine identity tjpHash (E: 123456789abcdef0123456789a0006)');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const pendingInfo = await getPendingStagingDir({
|
|
89
|
+
dataDir: reqCtx.dataDir,
|
|
90
|
+
tjpHash,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (logalot) { console.timeLog(timestampUUID, 'pending staging lookup complete'); }
|
|
94
|
+
|
|
95
|
+
if (!pendingInfo || pendingInfo.isExpired) {
|
|
96
|
+
// Check if email is already indexed in official space
|
|
97
|
+
const existing = await getIdentityIndexService().lookupIdentityByEmail({
|
|
98
|
+
email: normEmail,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (existing && existing.primaryTjpAddr === primaryTjpAddr) {
|
|
102
|
+
return this.ok({
|
|
103
|
+
success: true,
|
|
104
|
+
message: 'Email address is already verified and linked.',
|
|
105
|
+
alreadyVerified: true,
|
|
106
|
+
primaryTjpAddr,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return this.error(400, 'Verification record missing or expired. Please request a new verification code. (E: 123456789abcdef0123456789a0007)');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const pendingDir = pendingInfo.pendingDir;
|
|
114
|
+
const codeVerifyResult = await verifyPendingCode({
|
|
115
|
+
pendingDir,
|
|
116
|
+
code,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (logalot) { console.timeLog(timestampUUID, 'code verification check complete'); }
|
|
120
|
+
|
|
121
|
+
if (!codeVerifyResult.valid) {
|
|
122
|
+
const isRateLimited = codeVerifyResult.reason === 'too_many_attempts';
|
|
123
|
+
const statusCode = isRateLimited ? 429 : 400;
|
|
124
|
+
console.error(`${lc} Code verification failed for ${normEmail}: ${codeVerifyResult.error} (E: 123456789abcdef0123456789a0008)`);
|
|
125
|
+
return this.error(statusCode, codeVerifyResult.error || 'Incorrect verification code.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Mark email as verified in pending staging
|
|
129
|
+
const emailsDir = path.join(pendingDir, 'emails');
|
|
130
|
+
const emailHash = await hash({ s: `emailindex${normEmail}` });
|
|
131
|
+
const emailMetaPath = path.join(emailsDir, `${emailHash}.json`);
|
|
132
|
+
|
|
133
|
+
await fs.writeFile(
|
|
134
|
+
emailMetaPath,
|
|
135
|
+
JSON.stringify({ email: normEmail, verified: true, verifiedAt: Date.now() }, null, 2),
|
|
136
|
+
'utf-8'
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (logalot) { console.timeLog(timestampUUID, 'email metadata marked verified'); }
|
|
140
|
+
|
|
141
|
+
// Read all email metadata files to check 100% verification completion
|
|
142
|
+
const emailFiles = await fs.readdir(emailsDir);
|
|
143
|
+
const verifiedEmails: string[] = [];
|
|
144
|
+
let allVerified = true;
|
|
145
|
+
|
|
146
|
+
for (const file of emailFiles) {
|
|
147
|
+
if (file.endsWith('.json')) {
|
|
148
|
+
try {
|
|
149
|
+
const content = await fs.readFile(path.join(emailsDir, file), 'utf-8');
|
|
150
|
+
const meta = JSON.parse(content);
|
|
151
|
+
if (meta.verified === true && meta.email) {
|
|
152
|
+
verifiedEmails.push(meta.email);
|
|
153
|
+
} else {
|
|
154
|
+
allVerified = false;
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
allVerified = false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (allVerified) {
|
|
163
|
+
// 1. Promote keystone.json if present
|
|
164
|
+
const keystonePath = path.join(pendingDir, 'keystone.json');
|
|
165
|
+
if (existsSync(keystonePath)) {
|
|
166
|
+
try {
|
|
167
|
+
const keystoneContent = await fs.readFile(keystonePath, 'utf-8');
|
|
168
|
+
const keystone = JSON.parse(keystoneContent);
|
|
169
|
+
const addr = getIbGibAddr({ ibGib: keystone });
|
|
170
|
+
const metaspace = await bootstrapDomainMetaspace(addr, reqCtx.dataDir);
|
|
171
|
+
if (logalot) { console.timeLog(timestampUUID, 'domain metaspace bootstrap complete'); }
|
|
172
|
+
|
|
173
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
174
|
+
if (space) {
|
|
175
|
+
await metaspace.put({ ibGibs: [keystone], space });
|
|
176
|
+
await metaspace.registerNewIbGib({ ibGib: keystone, space });
|
|
177
|
+
}
|
|
178
|
+
if (logalot) { console.timeLog(timestampUUID, 'keystone promoted to domain space'); }
|
|
179
|
+
} catch (promoErr: any) {
|
|
180
|
+
console.error(`${lc} Failed to promote keystone.json to official space: ${extractErrorMsg(promoErr)} (E: 123456789abcdef0123456789a0009)`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 2. Register official reverse indices for verified emails
|
|
185
|
+
for (const ve of verifiedEmails) {
|
|
186
|
+
try {
|
|
187
|
+
await getIdentityIndexService().registerEmailIndex({
|
|
188
|
+
email: ve,
|
|
189
|
+
primaryTjpAddr,
|
|
190
|
+
});
|
|
191
|
+
} catch (indexErr: any) {
|
|
192
|
+
if (!indexErr?.message?.includes('duplicate_identity_409') && indexErr?.code !== 'EEXIST') {
|
|
193
|
+
console.error(`${lc} Failed to register official email index for ${ve}: ${extractErrorMsg(indexErr)} (E: 123456789abcdef0123456789a0010)`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (logalot) { console.timeLog(timestampUUID, 'official email indices registered'); }
|
|
198
|
+
|
|
199
|
+
// 3. Remove pending staging directory
|
|
200
|
+
try {
|
|
201
|
+
await fs.rm(pendingDir, { recursive: true, force: true });
|
|
202
|
+
} catch { /* ignore */ }
|
|
203
|
+
if (logalot) { console.timeLog(timestampUUID, 'pending staging directory cleaned'); }
|
|
204
|
+
|
|
205
|
+
console.log(`${lc} 100% verification achieved via 6-digit code. Promoted identity & registered indices for ${primaryTjpAddr} (I: 123456789abcdef0123456789a0011)`);
|
|
206
|
+
|
|
207
|
+
return this.ok({
|
|
208
|
+
success: true,
|
|
209
|
+
message: 'Email verified & account activated successfully!',
|
|
210
|
+
primaryTjpAddr,
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
return this.ok({
|
|
214
|
+
success: true,
|
|
215
|
+
message: 'Code verified for email. Remaining inboxes pending.',
|
|
216
|
+
primaryTjpAddr,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const emsg = extractErrorMsg(error);
|
|
222
|
+
console.error(`${lc} Error verifying code: ${emsg} (E: 123456789abcdef0123456789a0012)`);
|
|
223
|
+
return this.error(500, `Code verification error: ${emsg}`);
|
|
224
|
+
} finally {
|
|
225
|
+
if (logalot) {
|
|
226
|
+
console.log(`${lc} complete.`);
|
|
227
|
+
console.timeEnd(timestampUUID);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, rmSync, mkdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
respecfully, iReckon, ifWe, firstOfAll, ifWeMight // don't remove unused ifWe or ifWeMight
|
|
6
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
7
|
+
const sir = `[${import.meta.url}]`;
|
|
8
|
+
|
|
9
|
+
import { hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
createMagicLinkToken, verifyMagicLinkToken, markNonceUsed,
|
|
13
|
+
clearConsumedNonces, savePendingStagingKeystone, savePendingCodeRecord,
|
|
14
|
+
performPendingGc,
|
|
15
|
+
} from '../../../../email/index.mjs';
|
|
16
|
+
import { getIdentityIndexService, IdentityIndexService } from '../../../identity-index/identity-index-service.mjs';
|
|
17
|
+
import { EmailVerifyCodeOnetimeHandler } from './email-verify-code-onetime.handler.mjs';
|
|
18
|
+
import { ServeGib_V1 } from '../../../serve-gib-v1.mjs';
|
|
19
|
+
import { ErrorHandler } from '../../error-handler.mjs';
|
|
20
|
+
|
|
21
|
+
await respecfully(sir, '6-Digit Verification Code & Server Quarantine Policy', async () => {
|
|
22
|
+
const testDataDir = './test-data-verify';
|
|
23
|
+
const port = 3002;
|
|
24
|
+
|
|
25
|
+
let identityIndexSvc: IdentityIndexService;
|
|
26
|
+
firstOfAll(sir, async () => {
|
|
27
|
+
identityIndexSvc = getIdentityIndexService();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Clean test data directory
|
|
31
|
+
if (existsSync(testDataDir)) {
|
|
32
|
+
try { rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore */ }
|
|
33
|
+
}
|
|
34
|
+
mkdirSync(testDataDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
await ifWeMight(sir, 'generate, verify, and enforce expiration & single-use rules on magic link tokens', async () => {
|
|
37
|
+
clearConsumedNonces();
|
|
38
|
+
const email = 'alice@example.com';
|
|
39
|
+
const primaryTjpAddr = 'keystone^ABC123DEF456_TJP';
|
|
40
|
+
|
|
41
|
+
// 1. Create valid token
|
|
42
|
+
const token = createMagicLinkToken({ email, primaryTjpAddr });
|
|
43
|
+
iReckon(sir, token).asTo('magic link token').isGonnaBeTruthy();
|
|
44
|
+
|
|
45
|
+
// 2. Verify valid token
|
|
46
|
+
const verifyRes = verifyMagicLinkToken({ token });
|
|
47
|
+
iReckon(sir, verifyRes.valid).asTo('valid token verification').isGonnaBeTrue();
|
|
48
|
+
iReckon(sir, verifyRes.payload?.email).asTo('token email').isGonnaBe(email);
|
|
49
|
+
iReckon(sir, verifyRes.payload?.primaryTjpAddr).asTo('token primaryTjpAddr').isGonnaBe(primaryTjpAddr);
|
|
50
|
+
|
|
51
|
+
// 3. Reject expired token
|
|
52
|
+
const expiredToken = createMagicLinkToken({ email, primaryTjpAddr, ttlMs: -1000 });
|
|
53
|
+
const expiredRes = verifyMagicLinkToken({ token: expiredToken });
|
|
54
|
+
iReckon(sir, expiredRes.valid).asTo('expired token valid').isGonnaBeFalse();
|
|
55
|
+
iReckon(sir, expiredRes.error).includes('expired');
|
|
56
|
+
|
|
57
|
+
// 4. Reject tampered token
|
|
58
|
+
const parts = token.split('.');
|
|
59
|
+
const tamperedToken = `${parts[0]}.invalid_signature`;
|
|
60
|
+
const tamperedRes = verifyMagicLinkToken({ token: tamperedToken });
|
|
61
|
+
iReckon(sir, tamperedRes.valid).asTo('tampered signature valid').isGonnaBeFalse();
|
|
62
|
+
|
|
63
|
+
// 5. Enforce single-use nonce
|
|
64
|
+
if (verifyRes.payload) {
|
|
65
|
+
markNonceUsed(verifyRes.payload.nonce);
|
|
66
|
+
const reusedRes = verifyMagicLinkToken({ token });
|
|
67
|
+
iReckon(sir, reusedRes.valid).asTo('reused token valid').isGonnaBeFalse();
|
|
68
|
+
iReckon(sir, reusedRes.error).includes('already been used');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await ifWeMight(sir, 'create pending staging quarantine folder and AOT metadata files', async () => {
|
|
73
|
+
clearConsumedNonces();
|
|
74
|
+
const primaryTjpAddr = 'keystone^STAGING123_TJP';
|
|
75
|
+
const mockKeystone = {
|
|
76
|
+
ib: primaryTjpAddr,
|
|
77
|
+
gib: 'gib123',
|
|
78
|
+
data: { tjpAddr: primaryTjpAddr, username: 'staginguser' },
|
|
79
|
+
};
|
|
80
|
+
const emails = ['stage1@example.com', 'stage2@example.com'];
|
|
81
|
+
|
|
82
|
+
const saveRes = await savePendingStagingKeystone({
|
|
83
|
+
keystone: mockKeystone,
|
|
84
|
+
emails,
|
|
85
|
+
dataDir: testDataDir,
|
|
86
|
+
ttlMs: 60000,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
iReckon(sir, existsSync(saveRes.pendingDir)).asTo('pending dir exists').isGonnaBeTrue();
|
|
90
|
+
iReckon(sir, existsSync(join(saveRes.pendingDir, 'keystone.json'))).asTo('keystone.json exists').isGonnaBeTrue();
|
|
91
|
+
|
|
92
|
+
const salt = 'emailindex';
|
|
93
|
+
const tjpHash = await hash({ s: `${salt}${primaryTjpAddr}` });
|
|
94
|
+
iReckon(sir, saveRes.tjpHash).asTo('tjpHash matches').isGonnaBe(tjpHash);
|
|
95
|
+
|
|
96
|
+
const emailHash1 = await hash({ s: `${salt}stage1@example.com` });
|
|
97
|
+
const emailMetaPath1 = join(saveRes.pendingDir, 'emails', `${emailHash1}.json`);
|
|
98
|
+
iReckon(sir, existsSync(emailMetaPath1)).asTo('emailHash1 meta file exists').isGonnaBeTrue();
|
|
99
|
+
|
|
100
|
+
const meta1 = JSON.parse(readFileSync(emailMetaPath1, 'utf-8'));
|
|
101
|
+
iReckon(sir, meta1.email).asTo('meta1 email').isGonnaBe('stage1@example.com');
|
|
102
|
+
iReckon(sir, meta1.verified).asTo('meta1 initial verified status').isGonnaBeFalse();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await ifWeMight(sir, 'handle 6-digit OTP code verification and promote identity upon verification', async () => {
|
|
106
|
+
clearConsumedNonces();
|
|
107
|
+
const primaryTjpAddr = 'keystone^MULTIEMAIL_TJP';
|
|
108
|
+
const email = 'multi1@example.com';
|
|
109
|
+
const mockKeystone = {
|
|
110
|
+
ib: primaryTjpAddr,
|
|
111
|
+
gib: 'gib_multi',
|
|
112
|
+
data: { tjpAddr: primaryTjpAddr, emails: [email] },
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const saveRes = await savePendingStagingKeystone({
|
|
116
|
+
keystone: mockKeystone,
|
|
117
|
+
emails: [email],
|
|
118
|
+
primaryTjpAddr,
|
|
119
|
+
dataDir: testDataDir,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const testCode = '482901';
|
|
123
|
+
await savePendingCodeRecord({ pendingDir: saveRes.pendingDir, code: testCode });
|
|
124
|
+
|
|
125
|
+
const serveGib = new ServeGib_V1({
|
|
126
|
+
port,
|
|
127
|
+
dataDir: testDataDir,
|
|
128
|
+
handlers: [new EmailVerifyCodeOnetimeHandler()],
|
|
129
|
+
errorHandler: new ErrorHandler(),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const payload = JSON.stringify({
|
|
133
|
+
email,
|
|
134
|
+
primaryTjpAddr,
|
|
135
|
+
code: testCode,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const req: any = {
|
|
139
|
+
url: '/api/email/verify-code',
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: { 'content-type': 'application/json' },
|
|
142
|
+
on: (event: string, cb: any) => {
|
|
143
|
+
if (event === 'data') { cb(Buffer.from(payload)); }
|
|
144
|
+
if (event === 'end') { cb(); }
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let status = 0;
|
|
149
|
+
let body = '';
|
|
150
|
+
const res: any = {
|
|
151
|
+
writeHead: (st: number) => { status = st; },
|
|
152
|
+
end: (b: string) => { body = b; },
|
|
153
|
+
statusCode: 0,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
await serveGib.handleRequest(req, res);
|
|
157
|
+
|
|
158
|
+
iReckon(sir, status).asTo('email verify code status').isGonnaBe(200);
|
|
159
|
+
|
|
160
|
+
// Verify reverse index registered in official space
|
|
161
|
+
const record = await identityIndexSvc.lookupIdentityByEmail({ email });
|
|
162
|
+
iReckon(sir, record?.primaryTjpAddr).asTo('record primaryTjpAddr').isGonnaBe(primaryTjpAddr);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await ifWeMight(sir, 'execute atomic GC lockfile cleanup on expired staging directories', async () => {
|
|
166
|
+
clearConsumedNonces();
|
|
167
|
+
const expiredTjpAddr = 'keystone^EXPIRED_TJP';
|
|
168
|
+
const mockKeystone = { ib: expiredTjpAddr, data: { tjpAddr: expiredTjpAddr } };
|
|
169
|
+
|
|
170
|
+
const saveRes = await savePendingStagingKeystone({
|
|
171
|
+
keystone: mockKeystone,
|
|
172
|
+
emails: ['expired@example.com'],
|
|
173
|
+
dataDir: testDataDir,
|
|
174
|
+
ttlMs: -5000, // Expired 5 seconds ago
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
iReckon(sir, existsSync(saveRes.pendingDir)).asTo('expired pending dir created').isGonnaBeTrue();
|
|
178
|
+
|
|
179
|
+
const cleanedCount = await performPendingGc({ dataDir: testDataDir });
|
|
180
|
+
iReckon(sir, cleanedCount).asTo('GC cleaned expired dir count').isGonnaBe(1);
|
|
181
|
+
iReckon(sir, existsSync(saveRes.pendingDir)).asTo('expired pending dir removed').isGonnaBeFalse();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/handlers/api/identity/keystone-custodian-genesis.handler
|
|
3
|
+
*
|
|
4
|
+
* POST /api/identity/custodian/genesis
|
|
5
|
+
*
|
|
6
|
+
* Single-turn passwordless onboarding & custodian genesis endpoint.
|
|
7
|
+
* Returns the primary identity keystone (or primaryTjpAddr) along with a fresh,
|
|
8
|
+
* single-turn `CustodianRequirements` payload containing `authCode` and single-use `nonce`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'node:fs/promises';
|
|
12
|
+
import { extractErrorMsg, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
13
|
+
import { getTjpAddr } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
14
|
+
import { getDependencyGraph } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
|
|
15
|
+
import { FlatIbGibGraph } from '@ibgib/core-gib/dist/common/other/graph-types.mjs';
|
|
16
|
+
import { KeystoneProfileBuilder } from '@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs';
|
|
17
|
+
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
18
|
+
import { validateEmail } from '@ibgib/core-gib/dist/keystone/keystone-helpers.mjs';
|
|
19
|
+
|
|
20
|
+
import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
|
|
21
|
+
import { ServeGibHandlerBase } from '../../handler-base.mjs';
|
|
22
|
+
import { RequestContext, ResponseResult, ServeGibHttpMethod } from '../../../types.mjs';
|
|
23
|
+
import { API_PATH_REGEXES } from '../../../path-constants.mjs';
|
|
24
|
+
import { bootstrapDomainMetaspace } from '../../../bootstrap-helper.mjs';
|
|
25
|
+
import { getIdentityIndexService } from '../../../identity-index/identity-index-service.mjs';
|
|
26
|
+
import { normalizeEmail } from '../../../identity-index/identity-index-helpers.mjs';
|
|
27
|
+
import { loadSsoServerConfig } from '../../../../identity/sso/sso-config-helper.mjs';
|
|
28
|
+
import { generateCustodianRequirements } from '../../../../identity/sso/custodian-auth-helpers.mjs';
|
|
29
|
+
import { CustodianRequirements } from '../../../../identity/sso/sso-types.mjs';
|
|
30
|
+
import { getPendingStagingDir, getPendingTjpHash, verifyPendingCode } from '../../../../email/verify/email-verify-helpers.mjs';
|
|
31
|
+
|
|
32
|
+
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
33
|
+
|
|
34
|
+
export interface CustodianGenesisRequestBody {
|
|
35
|
+
email: string;
|
|
36
|
+
code?: string;
|
|
37
|
+
username?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CustodianGenesisResponseBody {
|
|
41
|
+
success: boolean;
|
|
42
|
+
isNewIdentity: boolean;
|
|
43
|
+
primaryTjpAddr: string;
|
|
44
|
+
primaryKeystone?: any;
|
|
45
|
+
primaryGraph?: FlatIbGibGraph;
|
|
46
|
+
custodianReqs: CustodianRequirements;
|
|
47
|
+
message?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* POST /api/identity/custodian/genesis
|
|
52
|
+
*/
|
|
53
|
+
export class KeystoneCustodianGenesisHandler extends ServeGibHandlerBase<undefined, undefined> {
|
|
54
|
+
protected override lc: string = `[${KeystoneCustodianGenesisHandler.name}]`;
|
|
55
|
+
protected override method: ServeGibHttpMethod = 'POST';
|
|
56
|
+
protected override regex = API_PATH_REGEXES.KEYSTONE_CUSTODIAN_GENESIS;
|
|
57
|
+
|
|
58
|
+
protected async handleRouteImpl(
|
|
59
|
+
reqCtx: RequestContext<undefined, undefined>
|
|
60
|
+
): Promise<ResponseResult | undefined> {
|
|
61
|
+
const lc = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
62
|
+
try {
|
|
63
|
+
if (logalot) { console.log(`${lc} starting... (I: a123456789abcdef0123456789b0001)`); }
|
|
64
|
+
|
|
65
|
+
if (reqCtx.method !== 'POST') {
|
|
66
|
+
return this.error(405, 'Method Not Allowed');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!reqCtx.body) {
|
|
70
|
+
return this.error(400, 'Request body is empty (E: b123456789abcdef0123456789b0002)');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let body: CustodianGenesisRequestBody;
|
|
74
|
+
try {
|
|
75
|
+
body = JSON.parse(reqCtx.body);
|
|
76
|
+
} catch {
|
|
77
|
+
return this.error(400, 'Request body must be valid JSON (E: c123456789abcdef0123456789b0003)');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { email, username, code } = body;
|
|
81
|
+
if (!email || !validateEmail(email)) {
|
|
82
|
+
return this.error(400, 'Invalid email address format (E: d123456789abcdef0123456789b0004)');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const normEmail = normalizeEmail(email);
|
|
86
|
+
const indexService = getIdentityIndexService();
|
|
87
|
+
const ssoConfig = loadSsoServerConfig();
|
|
88
|
+
const serverSecret = ssoConfig.ssoServerKdfSecret;
|
|
89
|
+
|
|
90
|
+
const existing = await indexService.lookupIdentityByEmail({ email: normEmail });
|
|
91
|
+
const effectivePrimaryTjpAddr = existing ? existing.primaryTjpAddr : `pending:${normEmail}`;
|
|
92
|
+
|
|
93
|
+
// Verify 6-digit OTP code if provided or if staging record exists
|
|
94
|
+
if (code || !existing) {
|
|
95
|
+
if (!code || typeof code !== 'string' || code.trim().length !== 6) {
|
|
96
|
+
return this.error(400, '6-digit verification code is required (E: e123456789abcdef0123456789b0009)');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const tjpHash = await getPendingTjpHash(effectivePrimaryTjpAddr);
|
|
100
|
+
const pendingInfo = reqCtx.dataDir && tjpHash ? await getPendingStagingDir({ dataDir: reqCtx.dataDir, tjpHash }) : undefined;
|
|
101
|
+
|
|
102
|
+
if (!pendingInfo || pendingInfo.isExpired) {
|
|
103
|
+
return this.error(400, 'Verification record missing or expired. Please request a new verification code. (E: f123456789abcdef0123456789b0010)');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const codeVerifyResult = await verifyPendingCode({
|
|
107
|
+
pendingDir: pendingInfo.pendingDir,
|
|
108
|
+
code,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!codeVerifyResult.valid) {
|
|
112
|
+
return this.error(400, codeVerifyResult.error || 'Invalid verification code (E: g123456789abcdef0123456789b0011)');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let primaryTjpAddr: string;
|
|
117
|
+
let primaryKeystone: any | undefined;
|
|
118
|
+
let isNewIdentity = false;
|
|
119
|
+
|
|
120
|
+
if (existing) {
|
|
121
|
+
// Identity already exists — retrieve primary identity info
|
|
122
|
+
primaryTjpAddr = existing.primaryTjpAddr;
|
|
123
|
+
console.log(`${lc} Found existing identity for ${normEmail}: ${primaryTjpAddr} (I: e123456789abcdef0123456789b0005)`);
|
|
124
|
+
|
|
125
|
+
// Bootstrap domain space to fetch primary keystone frame
|
|
126
|
+
const metaspace = await bootstrapDomainMetaspace(primaryTjpAddr, reqCtx.dataDir);
|
|
127
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
128
|
+
if (space) {
|
|
129
|
+
const latestAddr = await metaspace.getLatestAddr({ addr: primaryTjpAddr, space }) ?? primaryTjpAddr;
|
|
130
|
+
const getRes = await metaspace.get({ addrs: [latestAddr], space });
|
|
131
|
+
primaryKeystone = getRes.ibGibs?.[0];
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// New custodian identity creation
|
|
135
|
+
isNewIdentity = true;
|
|
136
|
+
console.log(`${lc} Creating new custodian primary identity for ${normEmail} (I: f123456789abcdef0123456789b0006)`);
|
|
137
|
+
|
|
138
|
+
const defaultUsername = username || normEmail.split('@')[0];
|
|
139
|
+
const primaryBuilder = KeystoneProfileBuilder.buildKeystone('sync')
|
|
140
|
+
.withUsername(defaultUsername)
|
|
141
|
+
.withDescription(`Full Custodian Primary Identity for ${normEmail}`)
|
|
142
|
+
.withDetails({
|
|
143
|
+
emails: [normEmail],
|
|
144
|
+
custodyMode: 'full',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const configs = await primaryBuilder.compileConfigs();
|
|
148
|
+
const primarySecret = await hash({ s: `${serverSecret}_custodian_${normEmail}` });
|
|
149
|
+
|
|
150
|
+
// Bootstrap temporary domain metaspace to perform genesis
|
|
151
|
+
const tempMetaspace = await bootstrapDomainMetaspace(`temp^${await getUUID()}`, reqCtx.dataDir);
|
|
152
|
+
const tempSpace = await tempMetaspace.getLocalUserSpace({ lock: false });
|
|
153
|
+
if (!tempSpace) {
|
|
154
|
+
throw new Error(`Failed to create temp domain space for new primary identity (E: 123456789abcdef0123456789b0007)`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const keystoneService = new KeystoneService_V1();
|
|
158
|
+
primaryKeystone = await keystoneService.genesis({
|
|
159
|
+
masterSecret: primarySecret,
|
|
160
|
+
emails: [normEmail],
|
|
161
|
+
frameDetails: primaryBuilder.getFrameDetails(),
|
|
162
|
+
configs,
|
|
163
|
+
metaspace: tempMetaspace,
|
|
164
|
+
space: tempSpace,
|
|
165
|
+
isPrimary: true
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
primaryTjpAddr = getTjpAddr({ ibGib: primaryKeystone, defaultIfNone: 'incomingAddr' })!;
|
|
169
|
+
|
|
170
|
+
// Bootstrap permanent domain space for primaryTjpAddr and persist primary keystone
|
|
171
|
+
const metaspace = await bootstrapDomainMetaspace(primaryTjpAddr, reqCtx.dataDir);
|
|
172
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
173
|
+
if (!space) {
|
|
174
|
+
throw new Error(`Failed to create domain space for new primary identity ${primaryTjpAddr} (E: 123456789abcdef0123456789b0008)`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
178
|
+
await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
|
|
179
|
+
|
|
180
|
+
// Register in identity reverse index
|
|
181
|
+
await indexService.registerEmailIndex({
|
|
182
|
+
email: normEmail,
|
|
183
|
+
primaryTjpAddr
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Clean up pending staging directory if present
|
|
187
|
+
const tjpHash = await getPendingTjpHash(effectivePrimaryTjpAddr);
|
|
188
|
+
const pendingInfo = reqCtx.dataDir && tjpHash ? await getPendingStagingDir({ dataDir: reqCtx.dataDir, tjpHash }) : undefined;
|
|
189
|
+
if (pendingInfo?.pendingDir) {
|
|
190
|
+
try { await fs.rm(pendingInfo.pendingDir, { recursive: true, force: true }); } catch { }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Generate single-turn CustodianRequirements for client delegate registration
|
|
195
|
+
const custodianReqs = await generateCustodianRequirements({
|
|
196
|
+
primaryTjpAddr,
|
|
197
|
+
serverSecret
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Extract complete primary keystone dependency graph
|
|
201
|
+
let primaryGraph: FlatIbGibGraph | undefined;
|
|
202
|
+
if (primaryKeystone) {
|
|
203
|
+
const domainMetaspace = await bootstrapDomainMetaspace(primaryTjpAddr, reqCtx.dataDir);
|
|
204
|
+
const domainSpace = await domainMetaspace.getLocalUserSpace({ lock: false });
|
|
205
|
+
if (domainSpace) {
|
|
206
|
+
primaryGraph = await getDependencyGraph({
|
|
207
|
+
ibGib: primaryKeystone,
|
|
208
|
+
space: domainSpace,
|
|
209
|
+
live: false
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const responseBody: CustodianGenesisResponseBody = {
|
|
215
|
+
success: true,
|
|
216
|
+
isNewIdentity,
|
|
217
|
+
primaryTjpAddr,
|
|
218
|
+
primaryKeystone,
|
|
219
|
+
primaryGraph,
|
|
220
|
+
custodianReqs,
|
|
221
|
+
message: isNewIdentity ? 'Custodian primary identity created.' : 'Custodian identity located.'
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return this.ok(responseBody, isNewIdentity ? 201 : 200);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const emsg = extractErrorMsg(error);
|
|
227
|
+
console.error(`${lc} ${emsg}`);
|
|
228
|
+
return this.error(500, emsg);
|
|
229
|
+
} finally {
|
|
230
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|