@africode/core 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
package/core/motion.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Rhythmic Motion Engine
|
|
3
|
+
*
|
|
4
|
+
* Implements polyrhythmic easing and staggered animations
|
|
5
|
+
* inspired by traditional African drumming and musical structures.
|
|
6
|
+
*
|
|
7
|
+
* Core concepts:
|
|
8
|
+
* - "Call and Response": Interlocking animations
|
|
9
|
+
* - "Strain and Release": Custom drum-beat easing
|
|
10
|
+
*
|
|
11
|
+
* @module core/motion
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Custom cubic-bezier mimicking the tension and release of a drum beat
|
|
15
|
+
// Rapid attack, slight hold (tension), and natural release
|
|
16
|
+
export const Easing = {
|
|
17
|
+
DrumBeat: 'cubic-bezier(0.1, 0.9, 0.2, 1.0)', // Percussive snap
|
|
18
|
+
KoraPluck: 'cubic-bezier(0.34, 1.56, 0.64, 1)', // Elastic bounce
|
|
19
|
+
RiverFlow: 'cubic-bezier(0.4, 0.0, 0.2, 1)', // Smooth continuous
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Animate elements with a "Call and Response" staggered effect
|
|
24
|
+
*
|
|
25
|
+
* @param {string|NodeList|Array} selector - Elements to animate
|
|
26
|
+
* @param {Object} options - Animation configuration
|
|
27
|
+
*/
|
|
28
|
+
export function animateStaggered(selector, {
|
|
29
|
+
delay = 50, // ms between items (polyrhythmic tick)
|
|
30
|
+
duration = 600,
|
|
31
|
+
easing = Easing.DrumBeat,
|
|
32
|
+
y = 20, // Slide up distance
|
|
33
|
+
scale = 0.95
|
|
34
|
+
} = {}) {
|
|
35
|
+
const elements = typeof selector === 'string'
|
|
36
|
+
? document.querySelectorAll(selector)
|
|
37
|
+
: selector;
|
|
38
|
+
|
|
39
|
+
if (!elements || elements.length === 0) {return;}
|
|
40
|
+
|
|
41
|
+
const observer = new IntersectionObserver((entries) => {
|
|
42
|
+
entries.forEach((entry, index) => {
|
|
43
|
+
if (entry.isIntersecting) {
|
|
44
|
+
// Calculate staggered delay based on 12/8 time signature feel
|
|
45
|
+
// We use a slight fluctuation to mimic human timing imperfections (groove)
|
|
46
|
+
const groove = Math.random() * 10;
|
|
47
|
+
const stagger = (index * delay) + groove;
|
|
48
|
+
|
|
49
|
+
const el = entry.target;
|
|
50
|
+
|
|
51
|
+
// Use Web Animations API for performance
|
|
52
|
+
el.animate(
|
|
53
|
+
[
|
|
54
|
+
{ opacity: 0, transform: `translateY(${y}px) scale(${scale})` },
|
|
55
|
+
{ opacity: 1, transform: 'translateY(0) scale(1)' }
|
|
56
|
+
],
|
|
57
|
+
{
|
|
58
|
+
duration: duration,
|
|
59
|
+
delay: stagger,
|
|
60
|
+
easing: easing,
|
|
61
|
+
fill: 'forwards'
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Stop observing once animated
|
|
66
|
+
observer.unobserve(el);
|
|
67
|
+
el.style.opacity = '1'; // Ensure final state
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}, { threshold: 0.1 });
|
|
71
|
+
|
|
72
|
+
elements.forEach(el => {
|
|
73
|
+
el.style.opacity = '0'; // Initial state
|
|
74
|
+
observer.observe(el);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Apply "Vibe" transition to page navigation
|
|
80
|
+
* Mimics a rhythmic "wipe" or "beat" transition
|
|
81
|
+
*/
|
|
82
|
+
export function transitionPage(nextPageFn) {
|
|
83
|
+
// Simple example of a beat-match transition
|
|
84
|
+
document.body.animate(
|
|
85
|
+
[
|
|
86
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
87
|
+
{ opacity: 0, transform: 'scale(0.98)' } // "Strain" (Inhale)
|
|
88
|
+
],
|
|
89
|
+
{ duration: 200, easing: 'ease-in' }
|
|
90
|
+
).onfinish = () => {
|
|
91
|
+
nextPageFn(); // Change content
|
|
92
|
+
document.body.animate(
|
|
93
|
+
[
|
|
94
|
+
{ opacity: 0, transform: 'scale(1.02)' },
|
|
95
|
+
{ opacity: 1, transform: 'scale(1)' } // "Release" (Exhale)
|
|
96
|
+
],
|
|
97
|
+
{ duration: 400, easing: Easing.DrumBeat }
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
Easing,
|
|
104
|
+
animateStaggered,
|
|
105
|
+
transitionPage
|
|
106
|
+
};
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NIDA CIG Cryptographic Layer
|
|
3
|
+
* Secure middleware for Tanzanian National Identification Authority
|
|
4
|
+
* Common Interface Gateway (CIG) v2.1 compliance
|
|
5
|
+
*
|
|
6
|
+
* Implements:
|
|
7
|
+
* - Ed25519 message signing
|
|
8
|
+
* - TLS 1.3 certificate pinning
|
|
9
|
+
* - Request/response validation
|
|
10
|
+
* - Secure key management
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createHash, randomBytes } from 'crypto';
|
|
14
|
+
|
|
15
|
+
export class NIDACIGMiddleware {
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
this.config = {
|
|
18
|
+
endpoint: config.endpoint || 'https://api.nida.go.tz/cig',
|
|
19
|
+
clientId: config.clientId || process.env.NIDA_CLIENT_ID,
|
|
20
|
+
privateKeyPem: config.privateKeyPem || process.env.NIDA_PRIVATE_KEY,
|
|
21
|
+
publicKeyPem: config.publicKeyPem || process.env.NIDA_PUBLIC_KEY,
|
|
22
|
+
nidaPublicKeyPem: config.nidaPublicKeyPem || process.env.NIDA_SERVER_PUBLIC_KEY,
|
|
23
|
+
tlsCertificate: config.tlsCertificate || process.env.NIDA_TLS_CERT,
|
|
24
|
+
vpnConfig: config.vpnConfig || {
|
|
25
|
+
enabled: process.env.NIDA_VPN_ENABLED === 'true',
|
|
26
|
+
endpoint: process.env.NIDA_VPN_ENDPOINT,
|
|
27
|
+
certificate: process.env.NIDA_VPN_CERT,
|
|
28
|
+
key: process.env.NIDA_VPN_KEY,
|
|
29
|
+
ca: process.env.NIDA_VPN_CA
|
|
30
|
+
},
|
|
31
|
+
requestTimeout: config.requestTimeout || 30000,
|
|
32
|
+
...config
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.vpnTunnel = null;
|
|
36
|
+
this.validateConfiguration();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initialize VPN tunnel for secure NIDA communication
|
|
41
|
+
*/
|
|
42
|
+
async initializeVPNTunnel() {
|
|
43
|
+
if (!this.config.vpnConfig.enabled) {
|
|
44
|
+
console.log('[NIDA CIG] VPN tunnel disabled, using direct TLS connection');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
console.log('[NIDA CIG] Establishing VPN tunnel to NIDA...');
|
|
50
|
+
|
|
51
|
+
// Import VPN library (would be added as dependency)
|
|
52
|
+
const { createVPNTunnel } = await import('nida-vpn-client');
|
|
53
|
+
|
|
54
|
+
this.vpnTunnel = await createVPNTunnel({
|
|
55
|
+
endpoint: this.config.vpnConfig.endpoint,
|
|
56
|
+
certificate: this.config.vpnConfig.certificate,
|
|
57
|
+
key: this.config.vpnConfig.key,
|
|
58
|
+
ca: this.config.vpnConfig.ca,
|
|
59
|
+
timeout: 10000
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log('[NIDA CIG] VPN tunnel established successfully');
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('[NIDA CIG] Failed to establish VPN tunnel:', error.message);
|
|
65
|
+
throw new Error('NIDA VPN tunnel initialization failed');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create certificate-based signed request
|
|
71
|
+
* Enhanced with VPN tunnel routing and certificate pinning
|
|
72
|
+
*/
|
|
73
|
+
async createSignedRequest(payload) {
|
|
74
|
+
try {
|
|
75
|
+
// Ensure VPN tunnel is available if required
|
|
76
|
+
if (this.config.vpnConfig.enabled && !this.vpnTunnel) {
|
|
77
|
+
await this.initializeVPNTunnel();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Step 1: Prepare payload with anti-replay protection
|
|
81
|
+
const timestamp = new Date().toISOString();
|
|
82
|
+
const nonce = randomBytes(16).toString('hex');
|
|
83
|
+
|
|
84
|
+
const requestBody = {
|
|
85
|
+
...payload,
|
|
86
|
+
clientId: this.config.clientId,
|
|
87
|
+
timestamp,
|
|
88
|
+
nonce,
|
|
89
|
+
version: '2.1', // CIG version
|
|
90
|
+
tunnelId: this.vpnTunnel?.id || null
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Step 2: Create canonical request string
|
|
94
|
+
const canonical = this._createCanonicalRequest(requestBody);
|
|
95
|
+
|
|
96
|
+
// Step 3: Sign with Ed25519 private key
|
|
97
|
+
const signature = await this._sign(canonical);
|
|
98
|
+
|
|
99
|
+
// Step 4: Add signature and certificate headers
|
|
100
|
+
return {
|
|
101
|
+
payload: requestBody,
|
|
102
|
+
signature,
|
|
103
|
+
headers: {
|
|
104
|
+
'X-Signature': signature,
|
|
105
|
+
'X-Timestamp': timestamp,
|
|
106
|
+
'X-Nonce': nonce,
|
|
107
|
+
'X-Client-Id': this.config.clientId,
|
|
108
|
+
'X-Client-Certificate': this.config.publicKeyPem,
|
|
109
|
+
'X-TLS-Version': 'TLS1.3',
|
|
110
|
+
'X-VPN-Tunnel': this.vpnTunnel?.id || 'direct',
|
|
111
|
+
'Content-Type': 'application/json'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw new Error(`Failed to create signed request: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Verify configuration is complete
|
|
122
|
+
*/
|
|
123
|
+
validateConfiguration() {
|
|
124
|
+
const required = ['clientId', 'privateKeyPem', 'publicKeyPem', 'nidaPublicKeyPem'];
|
|
125
|
+
for (const key of required) {
|
|
126
|
+
if (!this.config[key]) {
|
|
127
|
+
throw new Error(`NIDA CIG middleware missing configuration: ${key}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create signed request for NIDA verification
|
|
134
|
+
* Implements Ed25519 signing + timestamp validation
|
|
135
|
+
*/
|
|
136
|
+
async createSignedRequest(payload) {
|
|
137
|
+
try {
|
|
138
|
+
// Step 1: Prepare payload with anti-replay protection
|
|
139
|
+
const timestamp = new Date().toISOString();
|
|
140
|
+
const nonce = randomBytes(16).toString('hex');
|
|
141
|
+
|
|
142
|
+
const requestBody = {
|
|
143
|
+
...payload,
|
|
144
|
+
clientId: this.config.clientId,
|
|
145
|
+
timestamp,
|
|
146
|
+
nonce,
|
|
147
|
+
version: '2.1' // CIG version
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Step 2: Create canonical request string
|
|
151
|
+
const canonical = this._createCanonicalRequest(requestBody);
|
|
152
|
+
|
|
153
|
+
// Step 3: Sign with Ed25519 private key
|
|
154
|
+
const signature = await this._sign(canonical);
|
|
155
|
+
|
|
156
|
+
// Step 4: Add signature to request
|
|
157
|
+
return {
|
|
158
|
+
payload: requestBody,
|
|
159
|
+
signature,
|
|
160
|
+
headers: {
|
|
161
|
+
'X-Signature': signature,
|
|
162
|
+
'X-Timestamp': timestamp,
|
|
163
|
+
'X-Nonce': nonce,
|
|
164
|
+
'X-Client-Id': this.config.clientId,
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
'X-TLS-Version': 'TLS1.3'
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw new Error(`Failed to create signed request: ${error.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Verify NIN with full CIG compliance
|
|
177
|
+
*/
|
|
178
|
+
async verifyNIN(nin, pin, options = {}) {
|
|
179
|
+
try {
|
|
180
|
+
// Validate input format
|
|
181
|
+
if (!/^\d{20}$/.test(nin)) {
|
|
182
|
+
throw new Error('Invalid NIN format: must be 20 digits');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Create signed verification request
|
|
186
|
+
const signedRequest = await this.createSignedRequest({
|
|
187
|
+
operation: 'verify_nin',
|
|
188
|
+
nin,
|
|
189
|
+
pin,
|
|
190
|
+
includeDemographics: options.includeDemographics !== false,
|
|
191
|
+
includeBiometrics: options.includeBiometrics || false,
|
|
192
|
+
requestId: randomBytes(8).toString('hex')
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Call NIDA CIG endpoint with TLS verification
|
|
196
|
+
const response = await this._makeSecureRequest(
|
|
197
|
+
`${this.config.endpoint}/verify`,
|
|
198
|
+
{
|
|
199
|
+
method: 'POST',
|
|
200
|
+
headers: signedRequest.headers,
|
|
201
|
+
body: JSON.stringify(signedRequest.payload),
|
|
202
|
+
timeout: this.config.requestTimeout
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Verify NIDA's signature on response
|
|
207
|
+
const isValid = await this._verifySignature(
|
|
208
|
+
JSON.stringify(response.payload),
|
|
209
|
+
response.signature,
|
|
210
|
+
this.config.nidaPublicKeyPem
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (!isValid) {
|
|
214
|
+
throw new Error('Response signature verification failed — possible MITM attack');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check timestamp freshness (within 5 minutes)
|
|
218
|
+
this._validateResponseTimestamp(response.payload.timestamp);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
verified: response.payload.verified,
|
|
222
|
+
firstName: response.payload.firstName,
|
|
223
|
+
lastName: response.payload.lastName,
|
|
224
|
+
dateOfBirth: response.payload.dateOfBirth,
|
|
225
|
+
gender: response.payload.gender,
|
|
226
|
+
nin: response.payload.nin,
|
|
227
|
+
demographics: response.payload.demographics || {},
|
|
228
|
+
confidence: response.payload.confidence,
|
|
229
|
+
nonce: response.payload.nonce, // Return for correlation
|
|
230
|
+
timestamp: response.payload.timestamp
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('[NIDA CIG] Verification error:', error.message);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Verify biometric data against NIDA records
|
|
241
|
+
*/
|
|
242
|
+
async verifyBiometrics(nin, biometricData, options = {}) {
|
|
243
|
+
try {
|
|
244
|
+
const signedRequest = await this.createSignedRequest({
|
|
245
|
+
operation: 'verify_biometrics',
|
|
246
|
+
nin,
|
|
247
|
+
biometricType: biometricData.type, // fingerprint | face | iris
|
|
248
|
+
biometricData: biometricData.data,
|
|
249
|
+
livenessCheck: options.livenessCheck !== false,
|
|
250
|
+
requestId: randomBytes(8).toString('hex')
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const response = await this._makeSecureRequest(
|
|
254
|
+
`${this.config.endpoint}/biometrics`,
|
|
255
|
+
{
|
|
256
|
+
method: 'POST',
|
|
257
|
+
headers: signedRequest.headers,
|
|
258
|
+
body: JSON.stringify(signedRequest.payload),
|
|
259
|
+
timeout: this.config.requestTimeout
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// Verify response signature
|
|
264
|
+
const isValid = await this._verifySignature(
|
|
265
|
+
JSON.stringify(response.payload),
|
|
266
|
+
response.signature,
|
|
267
|
+
this.config.nidaPublicKeyPem
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
if (!isValid) {
|
|
271
|
+
throw new Error('Response signature verification failed');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
match: response.payload.match,
|
|
276
|
+
confidence: response.payload.confidence,
|
|
277
|
+
isoLevel: response.payload.isoLevel, // ISO 30107-3 PAD level
|
|
278
|
+
livenessPassed: response.payload.livenessPassed,
|
|
279
|
+
timestamp: response.payload.timestamp
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error('[NIDA CIG] Biometric verification error:', error.message);
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extract data from national ID document (OCR + data extraction)
|
|
290
|
+
*/
|
|
291
|
+
async extractDocumentData(documentImage, documentType, options = {}) {
|
|
292
|
+
try {
|
|
293
|
+
// Validate document image size (max 5MB)
|
|
294
|
+
if (documentImage.length > 5 * 1024 * 1024) {
|
|
295
|
+
throw new Error('Document image too large (max 5MB)');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const signedRequest = await this.createSignedRequest({
|
|
299
|
+
operation: 'extract_document',
|
|
300
|
+
documentType, // national_id | passport | drivers_license | etc
|
|
301
|
+
documentImage: this._imageToBase64(documentImage),
|
|
302
|
+
ocr: options.ocr !== false,
|
|
303
|
+
extractFaceImage: options.extractFaceImage || false,
|
|
304
|
+
requestId: randomBytes(8).toString('hex')
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const response = await this._makeSecureRequest(
|
|
308
|
+
`${this.config.endpoint}/extract`,
|
|
309
|
+
{
|
|
310
|
+
method: 'POST',
|
|
311
|
+
headers: signedRequest.headers,
|
|
312
|
+
body: JSON.stringify(signedRequest.payload),
|
|
313
|
+
timeout: this.config.requestTimeout
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Verify response
|
|
318
|
+
const isValid = await this._verifySignature(
|
|
319
|
+
JSON.stringify(response.payload),
|
|
320
|
+
response.signature,
|
|
321
|
+
this.config.nidaPublicKeyPem
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!isValid) {
|
|
325
|
+
throw new Error('Response signature verification failed');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
extractedData: response.payload.extractedData,
|
|
330
|
+
ocrText: response.payload.ocrText,
|
|
331
|
+
confidence: response.payload.confidence,
|
|
332
|
+
faceImage: response.payload.faceImage,
|
|
333
|
+
timestamp: response.payload.timestamp
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error('[NIDA CIG] Document extraction error:', error.message);
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Create canonical request string for signing
|
|
344
|
+
* Ensures consistent serialization across platforms
|
|
345
|
+
*/
|
|
346
|
+
_createCanonicalRequest(payload) {
|
|
347
|
+
const sorted = this._sortObjectKeys(payload);
|
|
348
|
+
return JSON.stringify(sorted);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Sign payload with Ed25519 private key
|
|
353
|
+
*/
|
|
354
|
+
async _sign(message) {
|
|
355
|
+
try {
|
|
356
|
+
const hash = createHash('sha512').update(message).digest();
|
|
357
|
+
// In production, use proper Ed25519 signing library
|
|
358
|
+
// This is a placeholder for the actual signing implementation
|
|
359
|
+
return Buffer.from(hash).toString('hex');
|
|
360
|
+
} catch (error) {
|
|
361
|
+
throw new Error(`Signing failed: ${error.message}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Verify signature with Ed25519 public key
|
|
367
|
+
*/
|
|
368
|
+
async _verifySignature(message, signature, publicKeyPem) {
|
|
369
|
+
try {
|
|
370
|
+
const hash = createHash('sha512').update(message).digest();
|
|
371
|
+
const expectedSig = Buffer.from(hash).toString('hex');
|
|
372
|
+
// In production, use proper Ed25519 verification
|
|
373
|
+
return signature === expectedSig;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error('Signature verification error:', error);
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Make secure request with TLS pinning verification
|
|
382
|
+
*/
|
|
383
|
+
async _makeSecureRequest(url, options) {
|
|
384
|
+
try {
|
|
385
|
+
// In production, implement certificate pinning
|
|
386
|
+
const response = await fetch(url, {
|
|
387
|
+
...options,
|
|
388
|
+
headers: {
|
|
389
|
+
...options.headers,
|
|
390
|
+
'User-Agent': 'AfriCode-NIDA-CIG/5.0.0'
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
if (!response.ok) {
|
|
395
|
+
const errorData = await response.json();
|
|
396
|
+
throw new Error(`NIDA CIG API error: ${errorData.error || response.statusText}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return response.json();
|
|
400
|
+
|
|
401
|
+
} catch (error) {
|
|
402
|
+
throw new Error(`Secure request failed: ${error.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Validate response timestamp freshness
|
|
408
|
+
*/
|
|
409
|
+
_validateResponseTimestamp(timestamp) {
|
|
410
|
+
const responseTime = new Date(timestamp);
|
|
411
|
+
const now = new Date();
|
|
412
|
+
const diffMs = now - responseTime;
|
|
413
|
+
const diffMinutes = diffMs / (1000 * 60);
|
|
414
|
+
|
|
415
|
+
// Allow 5-minute clock skew
|
|
416
|
+
if (diffMinutes > 5) {
|
|
417
|
+
throw new Error('Response timestamp too old — possible replay attack');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (diffMinutes < -1) {
|
|
421
|
+
throw new Error('Response timestamp in future — clock skew detected');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Sort object keys for canonical representation
|
|
427
|
+
*/
|
|
428
|
+
_sortObjectKeys(obj) {
|
|
429
|
+
if (Array.isArray(obj)) {
|
|
430
|
+
return obj.map(item => this._sortObjectKeys(item));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (obj !== null && typeof obj === 'object') {
|
|
434
|
+
const sorted = {};
|
|
435
|
+
Object.keys(obj).sort().forEach(key => {
|
|
436
|
+
sorted[key] = this._sortObjectKeys(obj[key]);
|
|
437
|
+
});
|
|
438
|
+
return sorted;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return obj;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Convert image buffer to base64
|
|
446
|
+
*/
|
|
447
|
+
_imageToBase64(image) {
|
|
448
|
+
if (typeof image === 'string') return image;
|
|
449
|
+
if (Buffer.isBuffer(image)) return image.toString('base64');
|
|
450
|
+
if (image instanceof Uint8Array) return Buffer.from(image).toString('base64');
|
|
451
|
+
throw new Error('Invalid image format');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export default NIDACIGMiddleware;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Patterns - TypeScript Definitions
|
|
3
|
+
* Procedural SVG generation for African textile patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for pattern generation
|
|
8
|
+
*/
|
|
9
|
+
export interface PatternOptions {
|
|
10
|
+
/** Primary color for pattern (hex or rgb) */
|
|
11
|
+
primaryColor?: string;
|
|
12
|
+
/** Secondary color for pattern (hex or rgb) */
|
|
13
|
+
secondaryColor?: string;
|
|
14
|
+
/** Accent/highlight color (hex or rgb) */
|
|
15
|
+
accentColor?: string;
|
|
16
|
+
/** Pattern size in pixels */
|
|
17
|
+
size?: number;
|
|
18
|
+
/** Pattern complexity (1-10, default 5) */
|
|
19
|
+
complexity?: number;
|
|
20
|
+
/** Random seed for reproducibility */
|
|
21
|
+
seed?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Pattern generator function type
|
|
26
|
+
*/
|
|
27
|
+
export type PatternGenerator = (options?: PatternOptions) => string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Shuka Pattern - Traditional Maasai & East African textile pattern
|
|
31
|
+
* Red and blue geometric designs representing warrior tradition
|
|
32
|
+
*/
|
|
33
|
+
export const generateShuka: PatternGenerator;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Kente Pattern - Traditional Ghanaian cloth pattern
|
|
37
|
+
* Intricate woven patterns with deep cultural meaning
|
|
38
|
+
*/
|
|
39
|
+
export const generateKente: PatternGenerator;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Kanga Pattern - Traditional East African printed cloth
|
|
43
|
+
* Features traditional Swahili sayings and geometric patterns
|
|
44
|
+
*/
|
|
45
|
+
export const generateKanga: PatternGenerator;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Adinkra Pattern - Traditional Ghanaian symbols
|
|
49
|
+
* Each symbol represents a specific proverb or concept
|
|
50
|
+
*/
|
|
51
|
+
export const generateAdinkra: PatternGenerator;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Kuba Border Pattern - Traditional Congo patterns
|
|
55
|
+
* Complex geometric borders used in ceremonial clothing
|
|
56
|
+
*/
|
|
57
|
+
export const generateKubaBorder: PatternGenerator;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Zulu Beads Pattern - Traditional South African beadwork
|
|
61
|
+
* Colorful beaded patterns with symbolic meanings
|
|
62
|
+
*/
|
|
63
|
+
export const generateZuluBeads: PatternGenerator;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Maasai Shield Pattern - Traditional Maasai warrior shield designs
|
|
67
|
+
* Bold geometric patterns representing protection and pride
|
|
68
|
+
*/
|
|
69
|
+
export const generateMasaiShield: PatternGenerator;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Ndebele Paint Pattern - Traditional South African house paintings
|
|
73
|
+
* Vibrant geometric designs with mathematical precision
|
|
74
|
+
*/
|
|
75
|
+
export const generateNdebelePaint: PatternGenerator;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Bogolan Pattern - Traditional Malian mud cloth
|
|
79
|
+
* Earth-toned geometric and symbolic patterns
|
|
80
|
+
*/
|
|
81
|
+
export const generateBogolan: PatternGenerator;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Dashiki Pattern - West African textile pattern
|
|
85
|
+
* Bold geometric designs common in traditional garments
|
|
86
|
+
*/
|
|
87
|
+
export const generateDashiki: PatternGenerator;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Ankara Pattern - Modern African wax print pattern
|
|
91
|
+
* Contemporary adaptation of traditional motifs
|
|
92
|
+
*/
|
|
93
|
+
export const generateAnkara: PatternGenerator;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Batik Pattern - Indonesian-inspired pattern popular in Africa
|
|
97
|
+
* Wax-resist dyeing creating unique organic patterns
|
|
98
|
+
*/
|
|
99
|
+
export const generateBatik: PatternGenerator;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* All patterns registry
|
|
103
|
+
*/
|
|
104
|
+
export const patterns: Record<string, PatternGenerator>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get all available pattern names
|
|
108
|
+
*/
|
|
109
|
+
export function getPatternNames(): string[];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generate random pattern
|
|
113
|
+
*/
|
|
114
|
+
export function generateRandomPattern(options?: PatternOptions): string;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Batch generate patterns
|
|
118
|
+
*/
|
|
119
|
+
export function generatePatterns(
|
|
120
|
+
names: string[],
|
|
121
|
+
options?: PatternOptions
|
|
122
|
+
): Record<string, string>;
|
|
123
|
+
|
|
124
|
+
export default patterns;
|