@tetherto/wdk-react-native-secure-storage 1.0.0-beta.1 → 1.0.0-beta.2

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/dist/utils.js ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.VALID_STORAGE_KEYS = void 0;
37
+ exports.isKeychainCredentials = isKeychainCredentials;
38
+ exports.createStorageKey = createStorageKey;
39
+ exports.getStorageKey = getStorageKey;
40
+ exports.withTimeout = withTimeout;
41
+ // External packages
42
+ const Crypto = __importStar(require("expo-crypto"));
43
+ // Internal modules
44
+ const constants_1 = require("./constants");
45
+ const errors_1 = require("./errors");
46
+ const validation_1 = require("./validation");
47
+ /**
48
+ * Type guard to check if a value is valid keychain credentials
49
+ *
50
+ * @param value - The value to check
51
+ * @returns true if value is valid keychain credentials with non-empty password
52
+ */
53
+ function isKeychainCredentials(value) {
54
+ if (value === false ||
55
+ value === null ||
56
+ typeof value !== 'object' ||
57
+ Array.isArray(value)) {
58
+ return false;
59
+ }
60
+ const obj = value;
61
+ return (typeof obj.password === 'string' &&
62
+ obj.password.length > 0 &&
63
+ typeof obj.username === 'string' &&
64
+ typeof obj.service === 'string' &&
65
+ (obj.storage === undefined || typeof obj.storage === 'string'));
66
+ }
67
+ /**
68
+ * Hash identifier using SHA-256 from expo-crypto
69
+ *
70
+ * @param str - Input string to hash
71
+ * @returns Promise that resolves to a 64-character hexadecimal SHA-256 hash
72
+ */
73
+ async function hashIdentifier(str) {
74
+ return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, str);
75
+ }
76
+ /**
77
+ * Valid storage key names
78
+ * These are the only allowed base keys for secure storage
79
+ */
80
+ exports.VALID_STORAGE_KEYS = [
81
+ 'wallet_encryption_key',
82
+ 'wallet_encrypted_seed',
83
+ 'wallet_encrypted_entropy',
84
+ ];
85
+ /**
86
+ * Runtime validation for storage keys
87
+ * Ensures only valid base keys are used at runtime
88
+ *
89
+ * @param key - The key to validate
90
+ * @throws {ValidationError} If the key is not a valid storage key
91
+ * @internal
92
+ */
93
+ function assertValidStorageKey(key) {
94
+ const validKey = exports.VALID_STORAGE_KEYS.find((k) => k === key);
95
+ if (!validKey) {
96
+ throw new errors_1.ValidationError(`Invalid storage key: ${key}. Valid keys are: ${exports.VALID_STORAGE_KEYS.join(', ')}`);
97
+ }
98
+ }
99
+ /**
100
+ * Create a StorageKey from a string with runtime validation
101
+ *
102
+ * @param key - The key string to convert to StorageKey
103
+ * @returns The validated StorageKey
104
+ * @throws {ValidationError} If the key is not a valid storage key
105
+ */
106
+ function createStorageKey(key) {
107
+ assertValidStorageKey(key);
108
+ // After validation, we know key is one of VALID_STORAGE_KEYS
109
+ return key;
110
+ }
111
+ /**
112
+ * Generate a secure storage key from base key and optional identifier
113
+ *
114
+ * @param baseKey - The base storage key (must be a valid StorageKey)
115
+ * @param identifier - Optional identifier (e.g., email) to support multiple wallets
116
+ * @returns Promise that resolves to the storage key
117
+ * @throws {ValidationError} If baseKey is not a valid storage key
118
+ */
119
+ async function getStorageKey(baseKey, identifier) {
120
+ // Runtime validation for type system bypasses (e.g., 'invalid_key' as any)
121
+ assertValidStorageKey(baseKey);
122
+ // Handle undefined/null early
123
+ if (identifier == null) {
124
+ return baseKey;
125
+ }
126
+ // Validate identifier first (this will throw for empty strings and invalid formats)
127
+ (0, validation_1.validateIdentifier)(identifier);
128
+ // Normalize: lowercase and trim (validation ensures it's not empty after trim)
129
+ const normalized = identifier.toLowerCase().trim();
130
+ // Use SHA-256 hash from expo-crypto to prevent collisions and ensure safe key format
131
+ // This is a battle-tested, production-ready solution
132
+ const hash = await hashIdentifier(normalized);
133
+ return `${baseKey}_${hash}`;
134
+ }
135
+ /**
136
+ * Create a timeout wrapper for promises
137
+ *
138
+ * **IMPORTANT:** Uses Promise.race() which does NOT cancel the underlying promise.
139
+ * The original promise continues executing after timeout (result is ignored).
140
+ * This is acceptable for keychain operations as they're fast and OS-bounded.
141
+ *
142
+ * @param promise - The promise to wrap
143
+ * @param timeoutMs - Timeout in milliseconds (should be validated via validateTimeout before calling)
144
+ * @param operation - Name of the operation for error messages
145
+ * @returns Promise that rejects on timeout
146
+ * @throws {ValidationError} If timeoutMs is invalid
147
+ * @throws {TimeoutError} If operation times out
148
+ */
149
+ async function withTimeout(promise, timeoutMs, operation) {
150
+ // Note: validateTimeout should be called before this function to ensure timeoutMs is valid.
151
+ // This function only performs basic safety checks for direct calls.
152
+ if (typeof timeoutMs !== 'number' || !isFinite(timeoutMs) || timeoutMs <= 0) {
153
+ throw new errors_1.ValidationError(`Invalid timeout value: ${timeoutMs}. Must be a positive finite number.`);
154
+ }
155
+ if (timeoutMs < constants_1.MIN_TIMEOUT_MS || timeoutMs > constants_1.MAX_TIMEOUT_MS) {
156
+ throw new errors_1.ValidationError(`Timeout ${timeoutMs}ms is out of range. Must be between ${constants_1.MIN_TIMEOUT_MS}ms and ${constants_1.MAX_TIMEOUT_MS}ms.`);
157
+ }
158
+ let timeout;
159
+ const timeoutPromise = new Promise((_, reject) => {
160
+ timeout = setTimeout(() => {
161
+ reject(new errors_1.TimeoutError(`Operation ${operation} timed out after ${timeoutMs}ms`));
162
+ }, timeoutMs);
163
+ });
164
+ try {
165
+ return await Promise.race([promise, timeoutPromise]);
166
+ }
167
+ finally {
168
+ clearTimeout(timeout);
169
+ }
170
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Authentication options for biometric prompts
3
+ */
4
+ interface AuthenticationOptions {
5
+ promptMessage?: string;
6
+ cancelLabel?: string;
7
+ disableDeviceFallback?: boolean;
8
+ }
9
+ /**
10
+ * Maximum length for identifier strings
11
+ */
12
+ export declare const MAX_IDENTIFIER_LENGTH = 256;
13
+ /**
14
+ * Maximum length for stored values (10KB)
15
+ */
16
+ export declare const MAX_VALUE_LENGTH = 10240;
17
+ /**
18
+ * Validates an identifier parameter
19
+ *
20
+ * @param identifier - The identifier to validate (optional)
21
+ * @throws {ValidationError} If identifier is invalid
22
+ */
23
+ export declare function validateIdentifier(identifier?: string): void;
24
+ /**
25
+ * Validates a value to be stored
26
+ *
27
+ * @param value - The value to validate
28
+ * @param fieldName - Name of the field for error messages
29
+ * @throws {ValidationError} If value is invalid
30
+ */
31
+ export declare function validateValue(value: string, fieldName?: string): void;
32
+ /**
33
+ * Validates a timeout value
34
+ *
35
+ * @param timeoutMs - The timeout value to validate (optional)
36
+ * @returns The validated timeout value, or undefined if not provided
37
+ * @throws {ValidationError} If timeout is invalid
38
+ */
39
+ export declare function validateTimeout(timeoutMs: number | undefined): number | undefined;
40
+ /**
41
+ * Validates authentication options
42
+ *
43
+ * @param options - The authentication options to validate (optional)
44
+ * @throws {ValidationError} If any option is invalid
45
+ */
46
+ export declare function validateAuthenticationOptions(options?: AuthenticationOptions): void;
47
+ export {};
48
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,UAAU,qBAAqB;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AAiBrC;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAyB5D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,MAAgB,GAAG,IAAI,CAkB9E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkBjF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CA4BnF"}
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_VALUE_LENGTH = exports.MAX_IDENTIFIER_LENGTH = void 0;
4
+ exports.validateIdentifier = validateIdentifier;
5
+ exports.validateValue = validateValue;
6
+ exports.validateTimeout = validateTimeout;
7
+ exports.validateAuthenticationOptions = validateAuthenticationOptions;
8
+ const errors_1 = require("./errors");
9
+ const constants_1 = require("./constants");
10
+ /**
11
+ * Maximum length for identifier strings
12
+ */
13
+ exports.MAX_IDENTIFIER_LENGTH = 256;
14
+ /**
15
+ * Maximum length for stored values (10KB)
16
+ */
17
+ exports.MAX_VALUE_LENGTH = 10240;
18
+ /**
19
+ * Pattern for valid identifiers
20
+ * Allows: alphanumeric, dots, dashes, underscores, plus signs, and optional email-like format
21
+ *
22
+ * Examples of valid identifiers:
23
+ * - "user123" (simple identifier)
24
+ * - "my_wallet" (with underscore)
25
+ * - "test-identifier" (with dash)
26
+ * - "user@example.com" (email format)
27
+ * - "user+tag@example.com" (email with plus sign in local part)
28
+ *
29
+ * The email part (after @) is optional - simple identifiers are fully supported.
30
+ */
31
+ const IDENTIFIER_PATTERN = /^[a-zA-Z0-9._+-]+(@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})?$/;
32
+ /**
33
+ * Validates an identifier parameter
34
+ *
35
+ * @param identifier - The identifier to validate (optional)
36
+ * @throws {ValidationError} If identifier is invalid
37
+ */
38
+ function validateIdentifier(identifier) {
39
+ if (identifier === undefined || identifier === null) {
40
+ return; // Optional parameter is allowed
41
+ }
42
+ if (typeof identifier !== 'string') {
43
+ throw new errors_1.ValidationError('Identifier must be a string');
44
+ }
45
+ const trimmed = identifier.trim();
46
+ if (trimmed === '') {
47
+ throw new errors_1.ValidationError('Identifier cannot be empty');
48
+ }
49
+ if (trimmed.length > exports.MAX_IDENTIFIER_LENGTH) {
50
+ throw new errors_1.ValidationError(`Identifier exceeds maximum length of ${exports.MAX_IDENTIFIER_LENGTH} characters`);
51
+ }
52
+ if (!IDENTIFIER_PATTERN.test(trimmed)) {
53
+ throw new errors_1.ValidationError('Identifier contains invalid characters. Allowed: alphanumeric, dots, dashes, underscores, plus signs, and email format');
54
+ }
55
+ }
56
+ /**
57
+ * Validates a value to be stored
58
+ *
59
+ * @param value - The value to validate
60
+ * @param fieldName - Name of the field for error messages
61
+ * @throws {ValidationError} If value is invalid
62
+ */
63
+ function validateValue(value, fieldName = 'value') {
64
+ if (value === null || value === undefined) {
65
+ throw new errors_1.ValidationError(`${fieldName} cannot be null or undefined`);
66
+ }
67
+ if (typeof value !== 'string') {
68
+ throw new errors_1.ValidationError(`${fieldName} must be a string`);
69
+ }
70
+ if (value.length === 0) {
71
+ throw new errors_1.ValidationError(`${fieldName} cannot be empty`);
72
+ }
73
+ if (value.length > exports.MAX_VALUE_LENGTH) {
74
+ throw new errors_1.ValidationError(`${fieldName} exceeds maximum length of ${exports.MAX_VALUE_LENGTH} characters`);
75
+ }
76
+ }
77
+ /**
78
+ * Validates a timeout value
79
+ *
80
+ * @param timeoutMs - The timeout value to validate (optional)
81
+ * @returns The validated timeout value, or undefined if not provided
82
+ * @throws {ValidationError} If timeout is invalid
83
+ */
84
+ function validateTimeout(timeoutMs) {
85
+ if (timeoutMs === undefined) {
86
+ return undefined;
87
+ }
88
+ if (typeof timeoutMs !== 'number' || isNaN(timeoutMs) || !isFinite(timeoutMs)) {
89
+ throw new errors_1.ValidationError(`Invalid timeout value: ${timeoutMs}. Must be a finite number.`);
90
+ }
91
+ if (timeoutMs < constants_1.MIN_TIMEOUT_MS) {
92
+ throw new errors_1.ValidationError(`Timeout ${timeoutMs}ms is too short. Minimum is ${constants_1.MIN_TIMEOUT_MS}ms.`);
93
+ }
94
+ if (timeoutMs > constants_1.MAX_TIMEOUT_MS) {
95
+ throw new errors_1.ValidationError(`Timeout ${timeoutMs}ms is too long. Maximum is ${constants_1.MAX_TIMEOUT_MS}ms.`);
96
+ }
97
+ return timeoutMs;
98
+ }
99
+ /**
100
+ * Validates authentication options
101
+ *
102
+ * @param options - The authentication options to validate (optional)
103
+ * @throws {ValidationError} If any option is invalid
104
+ */
105
+ function validateAuthenticationOptions(options) {
106
+ if (!options) {
107
+ return;
108
+ }
109
+ if (options.promptMessage !== undefined) {
110
+ if (typeof options.promptMessage !== 'string') {
111
+ throw new errors_1.ValidationError('Authentication promptMessage must be a string');
112
+ }
113
+ if (options.promptMessage.trim().length === 0) {
114
+ throw new errors_1.ValidationError('Authentication promptMessage cannot be empty');
115
+ }
116
+ }
117
+ if (options.cancelLabel !== undefined) {
118
+ if (typeof options.cancelLabel !== 'string') {
119
+ throw new errors_1.ValidationError('Authentication cancelLabel must be a string');
120
+ }
121
+ if (options.cancelLabel.trim().length === 0) {
122
+ throw new errors_1.ValidationError('Authentication cancelLabel cannot be empty');
123
+ }
124
+ }
125
+ if (options.disableDeviceFallback !== undefined) {
126
+ if (typeof options.disableDeviceFallback !== 'boolean') {
127
+ throw new errors_1.ValidationError('Authentication disableDeviceFallback must be a boolean');
128
+ }
129
+ }
130
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tetherto/wdk-react-native-secure-storage",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Secure storage abstractions for React Native - provides secure storage for sensitive data (encrypted seeds, keys) using react-native-keychain",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",