@oxyhq/services 5.16.35 → 5.16.37
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/README.md +8 -26
- package/lib/commonjs/core/OxyServices.base.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.utility.js.map +1 -1
- package/lib/commonjs/crypto/README.md +142 -0
- package/lib/commonjs/crypto/core.js +147 -0
- package/lib/commonjs/crypto/core.js.map +1 -0
- package/lib/commonjs/crypto/index.js +16 -0
- package/lib/commonjs/crypto/index.js.map +1 -1
- package/lib/commonjs/crypto/keyManager.js +19 -22
- package/lib/commonjs/crypto/keyManager.js.map +1 -1
- package/lib/commonjs/crypto/signatureService.js +116 -28
- package/lib/commonjs/crypto/signatureService.js.map +1 -1
- package/lib/commonjs/index.js +0 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/models/interfaces.js +10 -11
- package/lib/commonjs/models/interfaces.js.map +1 -1
- package/lib/commonjs/node/index.js +10 -1
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/node/signatureService.js +107 -0
- package/lib/commonjs/node/signatureService.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +23 -0
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +29 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useLanguageManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/useLanguageManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/index.js +0 -2
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +11 -2
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/core/OxyServices.base.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.utility.js.map +1 -1
- package/lib/module/crypto/README.md +142 -0
- package/lib/module/crypto/core.js +133 -0
- package/lib/module/crypto/core.js.map +1 -0
- package/lib/module/crypto/index.js +3 -9
- package/lib/module/crypto/index.js.map +1 -1
- package/lib/module/crypto/keyManager.js +19 -22
- package/lib/module/crypto/keyManager.js.map +1 -1
- package/lib/module/crypto/signatureService.js +113 -23
- package/lib/module/crypto/signatureService.js.map +1 -1
- package/lib/module/index.js +0 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/interfaces.js +10 -11
- package/lib/module/models/interfaces.js.map +1 -1
- package/lib/module/node/index.js +3 -0
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/node/signatureService.js +101 -0
- package/lib/module/node/signatureService.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +23 -0
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +29 -2
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/hooks/useLanguageManagement.js.map +1 -1
- package/lib/module/ui/hooks/useLanguageManagement.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/index.js +0 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +11 -2
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/typescript/core/OxyServices.base.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.analytics.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts +1 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.developer.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.karma.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.language.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.location.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.payment.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.privacy.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.security.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.user.d.ts +1 -2
- package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.utility.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +13 -13
- package/lib/typescript/core/mixins/index.d.ts.map +1 -1
- package/lib/typescript/core/services/SessionService.d.ts +1 -1
- package/lib/typescript/core/services/SessionService.d.ts.map +1 -1
- package/lib/typescript/crypto/core.d.ts +56 -0
- package/lib/typescript/crypto/core.d.ts.map +1 -0
- package/lib/typescript/crypto/index.d.ts +1 -9
- package/lib/typescript/crypto/index.d.ts.map +1 -1
- package/lib/typescript/crypto/keyManager.d.ts +13 -1
- package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
- package/lib/typescript/crypto/signatureService.d.ts +15 -9
- package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +68 -15
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/node/index.d.ts +1 -0
- package/lib/typescript/node/index.d.ts.map +1 -1
- package/lib/typescript/node/signatureService.d.ts +55 -0
- package/lib/typescript/node/signatureService.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +1 -2
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +1 -2
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useLanguageManagement.d.ts +1 -2
- package/lib/typescript/ui/context/hooks/useLanguageManagement.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useLanguageManagement.d.ts +1 -2
- package/lib/typescript/ui/hooks/useLanguageManagement.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts +1 -2
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/ui/index.d.ts +1 -1
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/utils/avatarUtils.d.ts +1 -1
- package/lib/typescript/ui/utils/avatarUtils.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/core/OxyServices.base.ts +1 -2
- package/src/core/mixins/OxyServices.auth.ts +1 -1
- package/src/core/mixins/OxyServices.user.ts +1 -2
- package/src/core/mixins/OxyServices.utility.ts +1 -2
- package/src/core/services/SessionService.ts +1 -1
- package/src/crypto/README.md +142 -0
- package/src/crypto/__tests__/core.test.ts +203 -0
- package/src/crypto/core.ts +142 -0
- package/src/crypto/index.ts +3 -10
- package/src/crypto/keyManager.ts +25 -21
- package/src/crypto/signatureService.ts +137 -36
- package/src/index.ts +2 -3
- package/src/models/interfaces.ts +73 -21
- package/src/node/index.ts +3 -0
- package/src/node/signatureService.ts +126 -0
- package/src/ui/context/OxyContext.tsx +26 -2
- package/src/ui/context/hooks/useAuthOperations.ts +33 -4
- package/src/ui/context/hooks/useLanguageManagement.ts +1 -2
- package/src/ui/hooks/auth/index.ts +2 -0
- package/src/ui/hooks/mutations/useAccountMutations.ts +1 -1
- package/src/ui/hooks/mutations/useServicesMutations.ts +1 -1
- package/src/ui/hooks/queries/useAccountQueries.ts +1 -1
- package/src/ui/hooks/useLanguageManagement.ts +1 -2
- package/src/ui/hooks/useSessionManagement.ts +1 -2
- package/src/ui/index.ts +1 -2
- package/src/ui/screens/AccountSettingsScreen.tsx +6 -6
- package/src/ui/screens/AccountSwitcherScreen.tsx +1 -1
- package/src/ui/screens/OxyAuthScreen.tsx +11 -2
- package/src/ui/screens/ProfileScreen.tsx +1 -1
- package/src/ui/stores/authStore.ts +1 -1
- package/src/ui/types/navigation.ts +1 -1
- package/src/ui/utils/avatarUtils.ts +1 -1
- package/lib/commonjs/core/services/AuthService.js +0 -156
- package/lib/commonjs/core/services/AuthService.js.map +0 -1
- package/lib/commonjs/core/services/SessionTransportService.js +0 -64
- package/lib/commonjs/core/services/SessionTransportService.js.map +0 -1
- package/lib/commonjs/core/services/UserService.js +0 -123
- package/lib/commonjs/core/services/UserService.js.map +0 -1
- package/lib/commonjs/core/services/index.js +0 -34
- package/lib/commonjs/core/services/index.js.map +0 -1
- package/lib/commonjs/shared/crypto/messageBuilders.js +0 -79
- package/lib/commonjs/shared/crypto/messageBuilders.js.map +0 -1
- package/lib/commonjs/shared/crypto/platform.js +0 -118
- package/lib/commonjs/shared/crypto/platform.js.map +0 -1
- package/lib/commonjs/shared/crypto/signature.js +0 -191
- package/lib/commonjs/shared/crypto/signature.js.map +0 -1
- package/lib/commonjs/shared/index.js +0 -94
- package/lib/commonjs/shared/index.js.map +0 -1
- package/lib/commonjs/shared/models/index.js +0 -2
- package/lib/commonjs/shared/models/index.js.map +0 -1
- package/lib/commonjs/shared/transport/index.js +0 -260
- package/lib/commonjs/shared/transport/index.js.map +0 -1
- package/lib/commonjs/shared/utils/index.js +0 -82
- package/lib/commonjs/shared/utils/index.js.map +0 -1
- package/lib/module/core/services/AuthService.js +0 -151
- package/lib/module/core/services/AuthService.js.map +0 -1
- package/lib/module/core/services/SessionTransportService.js +0 -59
- package/lib/module/core/services/SessionTransportService.js.map +0 -1
- package/lib/module/core/services/UserService.js +0 -118
- package/lib/module/core/services/UserService.js.map +0 -1
- package/lib/module/core/services/index.js +0 -16
- package/lib/module/core/services/index.js.map +0 -1
- package/lib/module/shared/crypto/messageBuilders.js +0 -70
- package/lib/module/shared/crypto/messageBuilders.js.map +0 -1
- package/lib/module/shared/crypto/platform.js +0 -112
- package/lib/module/shared/crypto/platform.js.map +0 -1
- package/lib/module/shared/crypto/signature.js +0 -186
- package/lib/module/shared/crypto/signature.js.map +0 -1
- package/lib/module/shared/index.js +0 -30
- package/lib/module/shared/index.js.map +0 -1
- package/lib/module/shared/models/index.js +0 -2
- package/lib/module/shared/models/index.js.map +0 -1
- package/lib/module/shared/transport/index.js +0 -254
- package/lib/module/shared/transport/index.js.map +0 -1
- package/lib/module/shared/utils/index.js +0 -74
- package/lib/module/shared/utils/index.js.map +0 -1
- package/lib/typescript/core/services/AuthService.d.ts +0 -50
- package/lib/typescript/core/services/AuthService.d.ts.map +0 -1
- package/lib/typescript/core/services/SessionTransportService.d.ts +0 -31
- package/lib/typescript/core/services/SessionTransportService.d.ts.map +0 -1
- package/lib/typescript/core/services/UserService.d.ts +0 -39
- package/lib/typescript/core/services/UserService.d.ts.map +0 -1
- package/lib/typescript/core/services/index.d.ts +0 -13
- package/lib/typescript/core/services/index.d.ts.map +0 -1
- package/lib/typescript/shared/crypto/messageBuilders.d.ts +0 -38
- package/lib/typescript/shared/crypto/messageBuilders.d.ts.map +0 -1
- package/lib/typescript/shared/crypto/platform.d.ts +0 -54
- package/lib/typescript/shared/crypto/platform.d.ts.map +0 -1
- package/lib/typescript/shared/crypto/signature.d.ts +0 -72
- package/lib/typescript/shared/crypto/signature.d.ts.map +0 -1
- package/lib/typescript/shared/index.d.ts +0 -20
- package/lib/typescript/shared/index.d.ts.map +0 -1
- package/lib/typescript/shared/models/index.d.ts +0 -163
- package/lib/typescript/shared/models/index.d.ts.map +0 -1
- package/lib/typescript/shared/transport/index.d.ts +0 -73
- package/lib/typescript/shared/transport/index.d.ts.map +0 -1
- package/lib/typescript/shared/utils/index.d.ts +0 -28
- package/lib/typescript/shared/utils/index.d.ts.map +0 -1
- package/src/core/services/AuthService.ts +0 -153
- package/src/core/services/SessionTransportService.ts +0 -69
- package/src/core/services/UserService.ts +0 -125
- package/src/core/services/index.ts +0 -14
- package/src/shared/crypto/messageBuilders.ts +0 -89
- package/src/shared/crypto/platform.ts +0 -140
- package/src/shared/crypto/signature.ts +0 -235
- package/src/shared/index.ts +0 -28
- package/src/shared/models/index.ts +0 -173
- package/src/shared/transport/index.ts +0 -349
- package/src/shared/utils/index.ts +0 -73
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Cryptographic Functions - Platform Agnostic
|
|
3
|
+
*
|
|
4
|
+
* This module contains the core signature verification logic
|
|
5
|
+
* that is shared between all platforms (React Native, Node.js, Web).
|
|
6
|
+
* Platform-specific implementations (hashing, random generation) are injected.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ec as EC } from 'elliptic';
|
|
10
|
+
import type { EC as ECType } from 'elliptic';
|
|
11
|
+
|
|
12
|
+
const ec = new EC('secp256k1');
|
|
13
|
+
|
|
14
|
+
// Constants for signature validation
|
|
15
|
+
export const CHALLENGE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
16
|
+
export const MAX_SIGNATURE_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Core signature verification using elliptic curve
|
|
20
|
+
* This is platform-agnostic and works everywhere
|
|
21
|
+
*/
|
|
22
|
+
export function verifySignatureCore(
|
|
23
|
+
messageHash: string,
|
|
24
|
+
signature: string,
|
|
25
|
+
publicKey: string
|
|
26
|
+
): boolean {
|
|
27
|
+
try {
|
|
28
|
+
const key = ec.keyFromPublic(publicKey, 'hex');
|
|
29
|
+
return key.verify(messageHash, signature);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate that a string is a valid public key
|
|
37
|
+
*/
|
|
38
|
+
export function isValidPublicKey(publicKey: string): boolean {
|
|
39
|
+
// Reject empty strings
|
|
40
|
+
if (!publicKey || publicKey.trim().length === 0) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
ec.keyFromPublic(publicKey, 'hex');
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate that a string is a valid private key
|
|
54
|
+
*/
|
|
55
|
+
export function isValidPrivateKey(privateKey: string): boolean {
|
|
56
|
+
// Reject empty strings
|
|
57
|
+
if (!privateKey || privateKey.trim().length === 0) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Private keys must be 64 hex characters (32 bytes)
|
|
62
|
+
if (!/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const keyPair = ec.keyFromPrivate(privateKey);
|
|
68
|
+
// Verify it can derive a public key and the key is valid
|
|
69
|
+
keyPair.getPublic('hex');
|
|
70
|
+
// Check that the private key is not zero (which would be invalid)
|
|
71
|
+
const priv = keyPair.getPrivate();
|
|
72
|
+
if (!priv || priv.isZero()) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get a shortened display version of a public key
|
|
83
|
+
* Format: first 8 chars...last 8 chars
|
|
84
|
+
*/
|
|
85
|
+
export function shortenPublicKey(publicKey: string): string {
|
|
86
|
+
if (publicKey.length <= 20) return publicKey;
|
|
87
|
+
return `${publicKey.slice(0, 8)}...${publicKey.slice(-8)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Derive public key from a private key (without storing)
|
|
92
|
+
*/
|
|
93
|
+
export function derivePublicKey(privateKey: string): string {
|
|
94
|
+
const keyPair = ec.keyFromPrivate(privateKey);
|
|
95
|
+
return keyPair.getPublic('hex');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check timestamp freshness
|
|
100
|
+
*/
|
|
101
|
+
export function isTimestampFresh(timestamp: number, maxAgeMs: number = MAX_SIGNATURE_AGE_MS): boolean {
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
return (now - timestamp) <= maxAgeMs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build authentication challenge message
|
|
108
|
+
* Format: auth:{publicKey}:{challenge}:{timestamp}
|
|
109
|
+
*/
|
|
110
|
+
export function buildAuthMessage(publicKey: string, challenge: string, timestamp: number): string {
|
|
111
|
+
return `auth:${publicKey}:${challenge}:${timestamp}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Build registration message
|
|
116
|
+
* Format: oxy:register:{publicKey}:{timestamp}
|
|
117
|
+
*/
|
|
118
|
+
export function buildRegistrationMessage(publicKey: string, timestamp: number): string {
|
|
119
|
+
return `oxy:register:${publicKey}:${timestamp}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Build request signature message
|
|
124
|
+
* Format: request:{publicKey}:{timestamp}:{canonicalString}
|
|
125
|
+
*/
|
|
126
|
+
export function buildRequestMessage(
|
|
127
|
+
publicKey: string,
|
|
128
|
+
timestamp: number,
|
|
129
|
+
data: Record<string, unknown>
|
|
130
|
+
): string {
|
|
131
|
+
const sortedKeys = Object.keys(data).sort();
|
|
132
|
+
const canonicalParts = sortedKeys.map(key => `${key}:${JSON.stringify(data[key])}`);
|
|
133
|
+
const canonicalString = canonicalParts.join('|');
|
|
134
|
+
return `request:${publicKey}:${timestamp}:${canonicalString}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the elliptic curve instance (for key generation)
|
|
139
|
+
*/
|
|
140
|
+
export function getEllipticCurve(): ECType {
|
|
141
|
+
return ec;
|
|
142
|
+
}
|
package/src/crypto/index.ts
CHANGED
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides cryptographic identity management for the Oxy ecosystem.
|
|
5
5
|
* Handles key generation, secure storage, and digital signatures.
|
|
6
|
-
*
|
|
7
|
-
* ⚠️ IMPORTANT: KeyManager is primarily intended for use in the Oxy Accounts app.
|
|
8
|
-
* While it's exported here for convenience, third-party applications should NOT
|
|
9
|
-
* generate or store user identities. The Accounts app is the identity wallet.
|
|
10
|
-
*
|
|
11
|
-
* For third-party apps:
|
|
12
|
-
* - Use SignatureService for signing operations (but keys come from Accounts app)
|
|
13
|
-
* - Do NOT call KeyManager.createIdentity() or KeyManager.importKeyPair()
|
|
14
|
-
* - The Oxy Accounts app handles all identity generation and key storage
|
|
15
6
|
*/
|
|
16
7
|
|
|
17
8
|
// Import polyfills first - this ensures Buffer is available for crypto libraries
|
|
@@ -25,7 +16,9 @@ export {
|
|
|
25
16
|
} from './signatureService';
|
|
26
17
|
export { type BackupData } from './types';
|
|
27
18
|
|
|
19
|
+
// Export core crypto utilities (shared across platforms)
|
|
20
|
+
export * from './core';
|
|
21
|
+
|
|
28
22
|
// Re-export for convenience
|
|
29
23
|
export { KeyManager as default } from './keyManager';
|
|
30
24
|
|
|
31
|
-
|
package/src/crypto/keyManager.ts
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Key Manager - ECDSA secp256k1 Key Generation and Storage
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* ⚠️ **FOR OXY ACCOUNTS APP ONLY**
|
|
5
|
+
*
|
|
6
|
+
* This module handles secure generation, storage, and retrieval of cryptographic keys.
|
|
5
7
|
* Private keys are stored securely using expo-secure-store and never leave the device.
|
|
8
|
+
*
|
|
9
|
+
* **IMPORTANT**: Third-party apps should NOT use KeyManager directly.
|
|
10
|
+
* Instead, use the OxyServices authentication flows which communicate with the
|
|
11
|
+
* Oxy Accounts app via deep links/QR codes to obtain user authorization.
|
|
12
|
+
*
|
|
13
|
+
* The Oxy Accounts app is the sole owner of the user's private key and identity.
|
|
14
|
+
* Other apps request authentication from the Accounts app, which signs challenges
|
|
15
|
+
* and returns authorization to the requesting app via the API.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link https://github.com/OxyHQ/OxyHQServices/blob/main/packages/services/src/crypto/README.md|Crypto Module Documentation}
|
|
6
18
|
*/
|
|
7
19
|
|
|
8
20
|
import { ec as EC } from 'elliptic';
|
|
9
21
|
import type { ECKeyPair } from 'elliptic';
|
|
10
22
|
import { Platform } from 'react-native';
|
|
11
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
isValidPublicKey as validatePublicKey,
|
|
25
|
+
isValidPrivateKey as validatePrivateKey,
|
|
26
|
+
derivePublicKey as derivePublicKeyFromPrivate,
|
|
27
|
+
shortenPublicKey as shortenKey,
|
|
28
|
+
getEllipticCurve,
|
|
29
|
+
} from './core';
|
|
12
30
|
|
|
13
31
|
// Lazy imports for React Native specific modules
|
|
14
32
|
let SecureStore: typeof import('expo-secure-store') | null = null;
|
|
15
33
|
let ExpoCrypto: typeof import('expo-crypto') | null = null;
|
|
16
34
|
|
|
17
|
-
const ec =
|
|
35
|
+
const ec = getEllipticCurve();
|
|
18
36
|
|
|
19
37
|
const STORAGE_KEYS = {
|
|
20
38
|
PRIVATE_KEY: 'oxy_identity_private_key',
|
|
@@ -505,34 +523,21 @@ export class KeyManager {
|
|
|
505
523
|
* Derive public key from a private key (without storing)
|
|
506
524
|
*/
|
|
507
525
|
static derivePublicKey(privateKey: string): string {
|
|
508
|
-
|
|
509
|
-
return keyPair.getPublic('hex');
|
|
526
|
+
return derivePublicKeyFromPrivate(privateKey);
|
|
510
527
|
}
|
|
511
528
|
|
|
512
529
|
/**
|
|
513
530
|
* Validate that a string is a valid public key
|
|
514
531
|
*/
|
|
515
532
|
static isValidPublicKey(publicKey: string): boolean {
|
|
516
|
-
|
|
517
|
-
ec.keyFromPublic(publicKey, 'hex');
|
|
518
|
-
return true;
|
|
519
|
-
} catch {
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
533
|
+
return validatePublicKey(publicKey);
|
|
522
534
|
}
|
|
523
535
|
|
|
524
536
|
/**
|
|
525
537
|
* Validate that a string is a valid private key
|
|
526
538
|
*/
|
|
527
539
|
static isValidPrivateKey(privateKey: string): boolean {
|
|
528
|
-
|
|
529
|
-
const keyPair = ec.keyFromPrivate(privateKey);
|
|
530
|
-
// Verify it can derive a public key
|
|
531
|
-
keyPair.getPublic('hex');
|
|
532
|
-
return true;
|
|
533
|
-
} catch {
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
540
|
+
return validatePrivateKey(privateKey);
|
|
536
541
|
}
|
|
537
542
|
|
|
538
543
|
/**
|
|
@@ -540,8 +545,7 @@ export class KeyManager {
|
|
|
540
545
|
* Format: first 8 chars...last 8 chars
|
|
541
546
|
*/
|
|
542
547
|
static shortenPublicKey(publicKey: string): string {
|
|
543
|
-
|
|
544
|
-
return sharedShortenPublicKey(publicKey);
|
|
548
|
+
return shortenKey(publicKey);
|
|
545
549
|
}
|
|
546
550
|
}
|
|
547
551
|
|
|
@@ -4,26 +4,88 @@
|
|
|
4
4
|
* Handles signing and verification of messages using ECDSA secp256k1.
|
|
5
5
|
* Used for authenticating requests and proving identity ownership.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* For
|
|
7
|
+
* This service provides async methods for cross-platform compatibility (React Native + Node).
|
|
8
|
+
* For Node.js-only synchronous operations, use the node/signatureService module.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { ec as EC } from 'elliptic';
|
|
12
11
|
import { KeyManager } from './keyManager';
|
|
13
12
|
import {
|
|
13
|
+
verifySignatureCore,
|
|
14
|
+
isValidPublicKey as validatePublicKey,
|
|
15
|
+
isTimestampFresh,
|
|
14
16
|
buildAuthMessage,
|
|
15
17
|
buildRegistrationMessage,
|
|
16
18
|
buildRequestMessage,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
shortenPublicKey as shortenKey,
|
|
20
|
+
getEllipticCurve,
|
|
21
|
+
} from './core';
|
|
22
|
+
|
|
23
|
+
// Lazy import for expo-crypto
|
|
24
|
+
let ExpoCrypto: typeof import('expo-crypto') | null = null;
|
|
25
|
+
|
|
26
|
+
const ec = getEllipticCurve();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if we're in a React Native environment
|
|
30
|
+
*/
|
|
31
|
+
function isReactNative(): boolean {
|
|
32
|
+
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if we're in a Node.js environment
|
|
37
|
+
*/
|
|
38
|
+
function isNodeJS(): boolean {
|
|
39
|
+
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
40
|
+
}
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Initialize expo-crypto module
|
|
44
|
+
*/
|
|
45
|
+
async function initExpoCrypto(): Promise<typeof import('expo-crypto')> {
|
|
46
|
+
if (!ExpoCrypto) {
|
|
47
|
+
ExpoCrypto = await import('expo-crypto');
|
|
48
|
+
}
|
|
49
|
+
return ExpoCrypto;
|
|
50
|
+
}
|
|
24
51
|
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Compute SHA-256 hash of a string
|
|
54
|
+
*/
|
|
55
|
+
async function sha256(message: string): Promise<string> {
|
|
56
|
+
// In React Native, always use expo-crypto
|
|
57
|
+
if (isReactNative() || !isNodeJS()) {
|
|
58
|
+
const Crypto = await initExpoCrypto();
|
|
59
|
+
return Crypto.digestStringAsync(
|
|
60
|
+
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
61
|
+
message
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// In Node.js, use Node's crypto module
|
|
66
|
+
// Use Function constructor to prevent Metro bundler from statically analyzing this require
|
|
67
|
+
// This ensures the require is only evaluated in Node.js runtime, not during Metro bundling
|
|
68
|
+
try {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
70
|
+
const getCrypto = new Function('return require("crypto")');
|
|
71
|
+
const crypto = getCrypto();
|
|
72
|
+
return crypto.createHash('sha256').update(message).digest('hex');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Fallback to expo-crypto if Node crypto fails
|
|
75
|
+
const Crypto = await initExpoCrypto();
|
|
76
|
+
return Crypto.digestStringAsync(
|
|
77
|
+
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
78
|
+
message
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SignedMessage {
|
|
84
|
+
message: string;
|
|
85
|
+
signature: string;
|
|
86
|
+
publicKey: string;
|
|
87
|
+
timestamp: number;
|
|
88
|
+
}
|
|
27
89
|
|
|
28
90
|
export interface AuthChallenge {
|
|
29
91
|
challenge: string;
|
|
@@ -34,23 +96,39 @@ export interface AuthChallenge {
|
|
|
34
96
|
export class SignatureService {
|
|
35
97
|
/**
|
|
36
98
|
* Generate a random challenge string (for offline use)
|
|
37
|
-
* Uses
|
|
99
|
+
* Uses expo-crypto in React Native, crypto.randomBytes in Node.js
|
|
38
100
|
*/
|
|
39
101
|
static async generateChallenge(): Promise<string> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
102
|
+
if (isReactNative() || !isNodeJS()) {
|
|
103
|
+
// Use expo-crypto for React Native (expo-random is deprecated)
|
|
104
|
+
const Crypto = await initExpoCrypto();
|
|
105
|
+
const randomBytes = await Crypto.getRandomBytesAsync(32);
|
|
106
|
+
return Array.from(randomBytes)
|
|
107
|
+
.map((b: number) => b.toString(16).padStart(2, '0'))
|
|
108
|
+
.join('');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Node.js fallback
|
|
112
|
+
try {
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
114
|
+
const getCrypto = new Function('return require("crypto")');
|
|
115
|
+
const crypto = getCrypto();
|
|
116
|
+
return crypto.randomBytes(32).toString('hex');
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// Fallback to expo-crypto if Node crypto fails
|
|
119
|
+
const Crypto = await initExpoCrypto();
|
|
120
|
+
const randomBytes = await Crypto.getRandomBytesAsync(32);
|
|
121
|
+
return Array.from(randomBytes)
|
|
122
|
+
.map((b: number) => b.toString(16).padStart(2, '0'))
|
|
123
|
+
.join('');
|
|
124
|
+
}
|
|
45
125
|
}
|
|
46
126
|
|
|
47
127
|
/**
|
|
48
128
|
* Hash a message using SHA-256
|
|
49
|
-
* Uses shared crypto adapter
|
|
50
129
|
*/
|
|
51
130
|
static async hashMessage(message: string): Promise<string> {
|
|
52
|
-
|
|
53
|
-
return adapter.sha256(message);
|
|
131
|
+
return sha256(message);
|
|
54
132
|
}
|
|
55
133
|
|
|
56
134
|
/**
|
|
@@ -63,8 +141,7 @@ export class SignatureService {
|
|
|
63
141
|
throw new Error('No identity found. Please create or import an identity first.');
|
|
64
142
|
}
|
|
65
143
|
|
|
66
|
-
const
|
|
67
|
-
const messageHash = await adapter.sha256(message);
|
|
144
|
+
const messageHash = await sha256(message);
|
|
68
145
|
const signature = keyPair.sign(messageHash);
|
|
69
146
|
return signature.toDER('hex');
|
|
70
147
|
}
|
|
@@ -75,18 +152,43 @@ export class SignatureService {
|
|
|
75
152
|
*/
|
|
76
153
|
static async signWithKey(message: string, privateKey: string): Promise<string> {
|
|
77
154
|
const keyPair = ec.keyFromPrivate(privateKey);
|
|
78
|
-
const
|
|
79
|
-
const messageHash = await adapter.sha256(message);
|
|
155
|
+
const messageHash = await sha256(message);
|
|
80
156
|
const signature = keyPair.sign(messageHash);
|
|
81
157
|
return signature.toDER('hex');
|
|
82
158
|
}
|
|
83
159
|
|
|
84
160
|
/**
|
|
85
161
|
* Verify a signature against a message and public key
|
|
86
|
-
* Uses shared SignatureService for verification
|
|
87
162
|
*/
|
|
88
163
|
static async verify(message: string, signature: string, publicKey: string): Promise<boolean> {
|
|
89
|
-
|
|
164
|
+
try {
|
|
165
|
+
const messageHash = await sha256(message);
|
|
166
|
+
return verifySignatureCore(messageHash, signature, publicKey);
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Synchronous verification (for Node.js backend)
|
|
174
|
+
* Uses crypto module directly for hashing
|
|
175
|
+
* Note: This method should only be used in Node.js environments
|
|
176
|
+
*/
|
|
177
|
+
static verifySync(message: string, signature: string, publicKey: string): boolean {
|
|
178
|
+
try {
|
|
179
|
+
if (!isNodeJS()) {
|
|
180
|
+
// In React Native, use async verify instead
|
|
181
|
+
throw new Error('verifySync should only be used in Node.js. Use verify() in React Native.');
|
|
182
|
+
}
|
|
183
|
+
// Use Function constructor to prevent Metro bundler from statically analyzing this require
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
185
|
+
const getCrypto = new Function('return require("crypto")');
|
|
186
|
+
const crypto = getCrypto();
|
|
187
|
+
const messageHash = crypto.createHash('sha256').update(message).digest('hex');
|
|
188
|
+
return verifySignatureCore(messageHash, signature, publicKey);
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
90
192
|
}
|
|
91
193
|
|
|
92
194
|
/**
|
|
@@ -113,7 +215,6 @@ export class SignatureService {
|
|
|
113
215
|
/**
|
|
114
216
|
* Verify a signed message object
|
|
115
217
|
* Checks both signature validity and timestamp freshness
|
|
116
|
-
* Uses shared SignatureService for verification
|
|
117
218
|
*/
|
|
118
219
|
static async verifySignedMessage(
|
|
119
220
|
signedMessage: SignedMessage,
|
|
@@ -128,7 +229,7 @@ export class SignatureService {
|
|
|
128
229
|
|
|
129
230
|
// Verify signature
|
|
130
231
|
const messageWithTimestamp = `${message}:${timestamp}`;
|
|
131
|
-
return
|
|
232
|
+
return SignatureService.verify(messageWithTimestamp, signature, publicKey);
|
|
132
233
|
}
|
|
133
234
|
|
|
134
235
|
/**
|
|
@@ -154,7 +255,6 @@ export class SignatureService {
|
|
|
154
255
|
|
|
155
256
|
/**
|
|
156
257
|
* Verify a challenge response
|
|
157
|
-
* Uses shared SignatureService for verification
|
|
158
258
|
*/
|
|
159
259
|
static async verifyChallengeResponse(
|
|
160
260
|
originalChallenge: string,
|
|
@@ -162,13 +262,14 @@ export class SignatureService {
|
|
|
162
262
|
maxAgeMs: number = 5 * 60 * 1000
|
|
163
263
|
): Promise<boolean> {
|
|
164
264
|
const { challenge: signature, publicKey, timestamp } = response;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
265
|
+
|
|
266
|
+
// Check timestamp freshness
|
|
267
|
+
if (!isTimestampFresh(timestamp, maxAgeMs)) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const message = buildAuthMessage(publicKey, originalChallenge, timestamp);
|
|
272
|
+
return SignatureService.verify(message, signature, publicKey);
|
|
172
273
|
}
|
|
173
274
|
|
|
174
275
|
/**
|
package/src/index.ts
CHANGED
|
@@ -48,12 +48,11 @@ export {
|
|
|
48
48
|
} from './utils/languageUtils';
|
|
49
49
|
export type { LanguageMetadata } from './utils/languageUtils';
|
|
50
50
|
|
|
51
|
-
// Shared models and utilities (bundled for external consumers)
|
|
52
|
-
export * from './shared';
|
|
53
|
-
|
|
54
51
|
// Type exports
|
|
55
52
|
export type {
|
|
56
53
|
OxyConfig,
|
|
54
|
+
User,
|
|
55
|
+
LoginResponse,
|
|
57
56
|
Notification,
|
|
58
57
|
Wallet,
|
|
59
58
|
Transaction,
|
package/src/models/interfaces.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Services Package Interfaces
|
|
3
|
-
*
|
|
4
|
-
* Package-specific interfaces. For shared models (User, Session, etc.),
|
|
5
|
-
* import directly from the shared module:
|
|
6
|
-
*
|
|
7
|
-
* import { User, LoginResponse, Session } from '../shared';
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
export interface OxyConfig {
|
|
11
2
|
baseURL: string;
|
|
12
3
|
cloudURL?: string;
|
|
@@ -30,8 +21,65 @@ export interface OxyConfig {
|
|
|
30
21
|
onRequestError?: (url: string, method: string, error: Error) => void;
|
|
31
22
|
}
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
/**
|
|
25
|
+
* User Model
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT:
|
|
28
|
+
* - id: MongoDB ObjectId (24 hex characters) - PRIMARY IDENTIFIER for all internal operations
|
|
29
|
+
* - publicKey: Cryptographic public key (130 hex characters) - LOOKUP KEY for authentication and identity operations
|
|
30
|
+
*
|
|
31
|
+
* Never use publicKey as an ID. Always use id (ObjectId) for:
|
|
32
|
+
* - Database queries
|
|
33
|
+
* - Session userId
|
|
34
|
+
* - Token userId
|
|
35
|
+
* - Socket room names
|
|
36
|
+
* - API route parameters (unless explicitly doing publicKey lookup)
|
|
37
|
+
*/
|
|
38
|
+
export interface User {
|
|
39
|
+
id: string; // MongoDB ObjectId - PRIMARY IDENTIFIER (always 24 hex chars)
|
|
40
|
+
publicKey: string; // Cryptographic public key - LOOKUP KEY (130 hex chars for secp256k1)
|
|
41
|
+
username: string;
|
|
42
|
+
email?: string;
|
|
43
|
+
// Avatar file id (asset id)
|
|
44
|
+
avatar?: string;
|
|
45
|
+
// Privacy and security settings
|
|
46
|
+
privacySettings?: {
|
|
47
|
+
[key: string]: unknown;
|
|
48
|
+
};
|
|
49
|
+
name?: {
|
|
50
|
+
first?: string;
|
|
51
|
+
last?: string;
|
|
52
|
+
full?: string; // virtual, not stored in DB, returned by API
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
};
|
|
55
|
+
bio?: string;
|
|
56
|
+
karma?: number;
|
|
57
|
+
location?: string;
|
|
58
|
+
website?: string;
|
|
59
|
+
createdAt?: string;
|
|
60
|
+
updatedAt?: string;
|
|
61
|
+
links?: Array<{
|
|
62
|
+
title?: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
image?: string;
|
|
65
|
+
link: string;
|
|
66
|
+
}>;
|
|
67
|
+
// Social counts
|
|
68
|
+
_count?: {
|
|
69
|
+
followers?: number;
|
|
70
|
+
following?: number;
|
|
71
|
+
};
|
|
72
|
+
accountExpiresAfterInactivityDays?: number | null; // Days of inactivity before account expires (null = never expire)
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface LoginResponse {
|
|
77
|
+
accessToken?: string;
|
|
78
|
+
refreshToken?: string;
|
|
79
|
+
token?: string; // For backwards compatibility
|
|
80
|
+
user: User;
|
|
81
|
+
message?: string;
|
|
82
|
+
}
|
|
35
83
|
|
|
36
84
|
export interface Notification {
|
|
37
85
|
id: string;
|
|
@@ -104,8 +152,17 @@ export interface TransactionResponse {
|
|
|
104
152
|
transaction: Transaction;
|
|
105
153
|
}
|
|
106
154
|
|
|
107
|
-
|
|
108
|
-
|
|
155
|
+
export interface PaginationInfo {
|
|
156
|
+
total: number;
|
|
157
|
+
limit: number;
|
|
158
|
+
offset: number;
|
|
159
|
+
hasMore: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface SearchProfilesResponse {
|
|
163
|
+
data: User[];
|
|
164
|
+
pagination: PaginationInfo;
|
|
165
|
+
}
|
|
109
166
|
|
|
110
167
|
export interface KarmaRule {
|
|
111
168
|
id: string;
|
|
@@ -318,6 +375,8 @@ export interface AssetUrlResponse {
|
|
|
318
375
|
|
|
319
376
|
export interface AssetDeleteSummary {
|
|
320
377
|
fileId: string;
|
|
378
|
+
wouldDelete: boolean;
|
|
379
|
+
affectedApps: string[];
|
|
321
380
|
remainingLinks: number;
|
|
322
381
|
variants: string[];
|
|
323
382
|
}
|
|
@@ -435,7 +494,6 @@ export interface AssetUploadProgress {
|
|
|
435
494
|
}
|
|
436
495
|
|
|
437
496
|
// Device Session interfaces
|
|
438
|
-
// Note: User type should be imported from the shared module
|
|
439
497
|
export interface DeviceSession {
|
|
440
498
|
sessionId: string;
|
|
441
499
|
deviceId: string;
|
|
@@ -444,13 +502,7 @@ export interface DeviceSession {
|
|
|
444
502
|
lastActive: string;
|
|
445
503
|
expiresAt: string;
|
|
446
504
|
isCurrent: boolean;
|
|
447
|
-
user?:
|
|
448
|
-
id: string;
|
|
449
|
-
publicKey: string;
|
|
450
|
-
username: string;
|
|
451
|
-
avatar?: string;
|
|
452
|
-
[key: string]: unknown;
|
|
453
|
-
}; // Partial User - import full User type from '../shared' if needed
|
|
505
|
+
user?: User;
|
|
454
506
|
createdAt?: string;
|
|
455
507
|
}
|
|
456
508
|
|
package/src/node/index.ts
CHANGED
|
@@ -13,5 +13,8 @@ export { OxyServices, OXY_CLOUD_URL, oxyClient };
|
|
|
13
13
|
export { Models }; // Export all models as a namespace
|
|
14
14
|
export * from '../models/interfaces'; // Export all models directly
|
|
15
15
|
|
|
16
|
+
// ------------- Node-Specific Crypto Exports -------------
|
|
17
|
+
export { SignatureService } from './signatureService';
|
|
18
|
+
|
|
16
19
|
// Default export for consistency or specific use cases if needed
|
|
17
20
|
export default OxyServices;
|