@maccesar/titools 2.0.7 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS-VERCEL-RESEARCH.md +172 -0
- package/README.md +408 -389
- package/agents/{ti-researcher.md → ti-pro.md} +14 -13
- package/bin/titools.js +16 -12
- package/lib/cleanup.js +95 -0
- package/lib/commands/agents.js +146 -70
- package/lib/commands/skills.js +349 -0
- package/lib/commands/uninstall.js +189 -154
- package/lib/commands/update.js +104 -21
- package/lib/config.js +26 -20
- package/lib/downloader.js +1 -1
- package/lib/installer.js +37 -97
- package/lib/platform.js +9 -5
- package/lib/prompts/checkboxCancel.js +264 -0
- package/lib/prompts/selectCancel.js +204 -0
- package/lib/symlink.js +19 -7
- package/lib/utils.js +17 -17
- package/package.json +10 -10
- package/skills/alloy-guides/SKILL.md +8 -30
- package/skills/alloy-guides/references/CLI_TASKS.md +14 -24
- package/skills/alloy-guides/references/CONCEPTS.md +5 -25
- package/skills/alloy-guides/references/CONTROLLERS.md +2 -19
- package/skills/alloy-guides/references/MODELS.md +40 -10
- package/skills/alloy-guides/references/PURGETSS.md +1 -11
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +8 -14
- package/skills/alloy-guides/references/VIEWS_STYLES.md +14 -25
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +2 -8
- package/skills/alloy-guides/references/VIEWS_XML.md +29 -40
- package/skills/alloy-guides/references/WIDGETS.md +1 -17
- package/skills/alloy-howtos/SKILL.md +1 -22
- package/skills/alloy-howtos/references/best_practices.md +0 -17
- package/skills/alloy-howtos/references/cli_reference.md +1 -23
- package/skills/alloy-howtos/references/config_files.md +86 -15
- package/skills/alloy-howtos/references/custom_tags.md +14 -13
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +3 -22
- package/skills/alloy-howtos/references/samples.md +4 -14
- package/skills/purgetss/SKILL.md +11 -37
- package/skills/purgetss/references/EXAMPLES.md +106 -17
- package/skills/purgetss/references/animation-system.md +0 -67
- package/skills/purgetss/references/apply-directive.md +0 -18
- package/skills/purgetss/references/arbitrary-values.md +0 -25
- package/skills/purgetss/references/class-index.md +0 -46
- package/skills/purgetss/references/cli-commands.md +0 -41
- package/skills/purgetss/references/configurable-properties.md +0 -55
- package/skills/purgetss/references/custom-rules.md +0 -14
- package/skills/purgetss/references/customization-deep-dive.md +0 -49
- package/skills/purgetss/references/dynamic-component-creation.md +56 -37
- package/skills/purgetss/references/grid-layout.md +0 -32
- package/skills/purgetss/references/icon-fonts.md +0 -50
- package/skills/purgetss/references/installation-setup.md +61 -32
- package/skills/purgetss/references/migration-guide.md +127 -0
- package/skills/purgetss/references/opacity-modifier.md +0 -34
- package/skills/purgetss/references/performance-tips.md +261 -0
- package/skills/purgetss/references/platform-modifiers.md +0 -46
- package/skills/purgetss/references/smart-mappings.md +0 -12
- package/skills/purgetss/references/tikit-components.md +393 -0
- package/skills/purgetss/references/titanium-resets.md +1 -27
- package/skills/purgetss/references/ui-ux-design.md +467 -117
- package/skills/{alloy-expert → ti-expert}/SKILL.md +45 -54
- package/skills/ti-expert/references/alloy-builtins.md +387 -0
- package/skills/{alloy-expert → ti-expert}/references/alloy-structure.md +147 -40
- package/skills/ti-expert/references/anti-patterns.md +90 -0
- package/skills/ti-expert/references/cli-expert.md +109 -0
- package/skills/{alloy-expert → ti-expert}/references/code-conventions.md +182 -49
- package/skills/{alloy-expert → ti-expert}/references/controller-patterns.md +103 -93
- package/skills/{alloy-expert → ti-expert}/references/error-handling.md +12 -12
- package/skills/{alloy-expert → ti-expert}/references/examples.md +144 -85
- package/skills/{alloy-expert → ti-expert}/references/migration-patterns.md +38 -36
- package/skills/{alloy-expert → ti-expert}/references/patterns.md +8 -8
- package/skills/ti-expert/references/performance-listview.md +251 -0
- package/skills/{alloy-expert/references/performance-patterns.md → ti-expert/references/performance-optimization.md} +67 -283
- package/skills/{alloy-expert/references/security-patterns.md → ti-expert/references/security-device.md} +12 -295
- package/skills/ti-expert/references/security-fundamentals.md +284 -0
- package/skills/{alloy-expert → ti-expert}/references/state-management.md +10 -10
- package/skills/ti-expert/references/testing-e2e-ci.md +432 -0
- package/skills/ti-expert/references/testing-unit.md +433 -0
- package/skills/ti-expert/references/theming.md +394 -0
- package/skills/ti-guides/SKILL.md +1 -14
- package/skills/ti-guides/references/advanced-data-and-images.md +126 -10
- package/skills/ti-guides/references/app-distribution.md +48 -10
- package/skills/ti-guides/references/application-frameworks.md +9 -2
- package/skills/ti-guides/references/cli-reference.md +338 -82
- package/skills/ti-guides/references/coding-best-practices.md +14 -4
- package/skills/ti-guides/references/commonjs-advanced.md +103 -6
- package/skills/ti-guides/references/hello-world.md +5 -2
- package/skills/ti-guides/references/hyperloop-native-access.md +439 -46
- package/skills/ti-guides/references/javascript-primer.md +4 -3
- package/skills/ti-guides/references/resources.md +0 -1
- package/skills/ti-guides/references/style-and-conventions.md +57 -9
- package/skills/ti-guides/references/tiapp-config.md +48 -6
- package/skills/ti-howtos/SKILL.md +2 -27
- package/skills/ti-howtos/references/android-platform-deep-dives.md +41 -90
- package/skills/ti-howtos/references/automation-fastlane-appium.md +15 -14
- package/skills/ti-howtos/references/buffer-codec-streams.md +22 -22
- package/skills/ti-howtos/references/cross-platform-development.md +68 -37
- package/skills/ti-howtos/references/debugging-profiling.md +8 -53
- package/skills/ti-howtos/references/extending-titanium.md +12 -51
- package/skills/ti-howtos/references/google-maps-v2.md +30 -29
- package/skills/ti-howtos/references/ios-map-kit.md +19 -25
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +95 -283
- package/skills/ti-howtos/references/local-data-sources.md +45 -37
- package/skills/ti-howtos/references/location-and-maps.md +47 -28
- package/skills/ti-howtos/references/media-apis.md +84 -27
- package/skills/ti-howtos/references/notification-services.md +40 -89
- package/skills/ti-howtos/references/remote-data-sources.md +21 -36
- package/skills/ti-howtos/references/tutorials.md +79 -39
- package/skills/ti-howtos/references/using-modules.md +4 -30
- package/skills/ti-howtos/references/web-content-integration.md +28 -45
- package/skills/ti-howtos/references/webpack-build-pipeline.md +54 -18
- package/skills/ti-ui/SKILL.md +1 -21
- package/skills/ti-ui/references/accessibility-deep-dive.md +71 -25
- package/skills/ti-ui/references/animation-and-matrices.md +0 -47
- package/skills/ti-ui/references/application-structures.md +0 -46
- package/skills/ti-ui/references/custom-fonts-styling.md +9 -57
- package/skills/ti-ui/references/event-handling.md +32 -40
- package/skills/ti-ui/references/gestures.md +0 -40
- package/skills/ti-ui/references/icons-and-splash-screens.md +21 -44
- package/skills/ti-ui/references/layouts-and-positioning.md +47 -39
- package/skills/ti-ui/references/listviews-and-performance.md +187 -73
- package/skills/ti-ui/references/orientation.md +57 -71
- package/skills/ti-ui/references/platform-ui-android.md +114 -48
- package/skills/ti-ui/references/platform-ui-ios.md +63 -38
- package/skills/ti-ui/references/scrolling-views.md +14 -23
- package/skills/ti-ui/references/tableviews.md +6 -56
- package/AGENTS-TEMPLATE.md +0 -174
- package/lib/commands/install.js +0 -188
- package/skills/alloy-expert/references/anti-patterns.md +0 -133
- package/skills/alloy-expert/references/testing.md +0 -872
- package/skills/ti-guides/references/alloy-cli-advanced.md +0 -84
- package/skills/ti-guides/references/alloy-data-mastery.md +0 -29
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +0 -19
- /package/skills/{alloy-expert → ti-expert}/assets/ControllerAutoCleanup.js +0 -0
- /package/skills/{alloy-expert → ti-expert}/references/contracts.md +0 -0
|
@@ -1,287 +1,4 @@
|
|
|
1
|
-
# Security
|
|
2
|
-
|
|
3
|
-
## Token Storage Strategy
|
|
4
|
-
|
|
5
|
-
**NEVER store tokens in:** `Ti.App.Properties` (plaintext), localStorage, or files.
|
|
6
|
-
|
|
7
|
-
**USE platform-specific secure storage:**
|
|
8
|
-
|
|
9
|
-
```javascript
|
|
10
|
-
// lib/services/tokenStorage.js
|
|
11
|
-
exports.TokenStorage = {
|
|
12
|
-
save(token) {
|
|
13
|
-
if (Ti.Platform.osname === 'android') {
|
|
14
|
-
// Use Android KeyStore
|
|
15
|
-
const keyStore = Ti.Android.createKeyStore({
|
|
16
|
-
name: 'SecureKeyStore'
|
|
17
|
-
})
|
|
18
|
-
keyStore.addEntry('authToken', token)
|
|
19
|
-
} else {
|
|
20
|
-
// Use iOS Keychain
|
|
21
|
-
Ti.KeychainItem.setItem({
|
|
22
|
-
identifier: 'authToken',
|
|
23
|
-
value: token,
|
|
24
|
-
accessGroup: 'com.yourapp.keychain'
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
get() {
|
|
30
|
-
if (Ti.Platform.osname === 'android') {
|
|
31
|
-
const keyStore = Ti.Android.createKeyStore({
|
|
32
|
-
name: 'SecureKeyStore'
|
|
33
|
-
})
|
|
34
|
-
return keyStore.getEntry('authToken')
|
|
35
|
-
} else {
|
|
36
|
-
return Ti.KeychainItem.getItem({
|
|
37
|
-
identifier: 'authToken',
|
|
38
|
-
accessGroup: 'com.yourapp.keychain'
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
clear() {
|
|
44
|
-
if (Ti.Platform.osname === 'android') {
|
|
45
|
-
const keyStore = Ti.Android.createKeyStore({
|
|
46
|
-
name: 'SecureKeyStore'
|
|
47
|
-
})
|
|
48
|
-
keyStore.removeEntry('authToken')
|
|
49
|
-
} else {
|
|
50
|
-
Ti.KeychainItem.removeItem({
|
|
51
|
-
identifier: 'authToken',
|
|
52
|
-
accessGroup: 'com.yourapp.keychain'
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Certificate Pinning
|
|
60
|
-
|
|
61
|
-
Prevent man-in-the-middle attacks by pinning SSL certificates:
|
|
62
|
-
|
|
63
|
-
```javascript
|
|
64
|
-
// lib/api/pinnedClient.js
|
|
65
|
-
exports.createPinnedClient = function() {
|
|
66
|
-
const client = Ti.Network.createHTTPClient({
|
|
67
|
-
// Security: Enable certificate pinning
|
|
68
|
-
certificatePinning: true,
|
|
69
|
-
|
|
70
|
-
// Specify allowed certificates
|
|
71
|
-
validatesSecureCertificate: true,
|
|
72
|
-
|
|
73
|
-
onload: () => {
|
|
74
|
-
// Success
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
onerror: (e) => {
|
|
78
|
-
// Certificate validation failed
|
|
79
|
-
if (e.error.indexOf('certificate') >= 0) {
|
|
80
|
-
Ti.API.error('Certificate pinning failed - possible MITM attack')
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
return client
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**Add certificates to tiapp.xml:**
|
|
90
|
-
|
|
91
|
-
```xml
|
|
92
|
-
<ti:app>
|
|
93
|
-
<certificates>
|
|
94
|
-
<certificate>
|
|
95
|
-
<name>api.example.com</name>
|
|
96
|
-
<type>rsa</type>
|
|
97
|
-
<file>certificates/api-pin.pem</file>
|
|
98
|
-
</certificate>
|
|
99
|
-
</certificates>
|
|
100
|
-
</ti:app>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Data Encryption at Rest
|
|
104
|
-
|
|
105
|
-
```javascript
|
|
106
|
-
// lib/services/encryption.js
|
|
107
|
-
// AES-256 encryption for sensitive local data
|
|
108
|
-
|
|
109
|
-
const crypto = require('ti.crypto')
|
|
110
|
-
|
|
111
|
-
exports.encrypt = function(data, key) {
|
|
112
|
-
return crypto.encrypt({
|
|
113
|
-
data: data,
|
|
114
|
-
key: key,
|
|
115
|
-
algorithm: crypto.AES_256_CBC,
|
|
116
|
-
options: { mode: crypto.CBC }
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
exports.decrypt = function(encryptedData, key) {
|
|
121
|
-
return crypto.decrypt({
|
|
122
|
-
data: encryptedData,
|
|
123
|
-
key: key,
|
|
124
|
-
algorithm: crypto.AES_256_CBC,
|
|
125
|
-
options: { mode: crypto.CBC }
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Usage: Secure cache of sensitive user data
|
|
130
|
-
module.exports = class SecureCache {
|
|
131
|
-
constructor(encryptionKey) {
|
|
132
|
-
this.key = encryptionKey
|
|
133
|
-
this.cache = {}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
set(key, value) {
|
|
137
|
-
const encrypted = encrypt(JSON.stringify(value), this.key)
|
|
138
|
-
this.cache[key] = encrypted
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
get(key) {
|
|
142
|
-
if (!this.cache[key]) return null
|
|
143
|
-
|
|
144
|
-
const decrypted = decrypt(this.cache[key], this.key)
|
|
145
|
-
return JSON.parse(decrypted)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
clear() {
|
|
149
|
-
this.cache = {}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## Secure HTTP Communication
|
|
155
|
-
|
|
156
|
-
```javascript
|
|
157
|
-
// lib/api/secureClient.js
|
|
158
|
-
exports.createSecureClient = function(baseUrl) {
|
|
159
|
-
return {
|
|
160
|
-
request(method, endpoint, data = null) {
|
|
161
|
-
return new Promise((resolve, reject) => {
|
|
162
|
-
const client = Ti.Network.createHTTPClient({
|
|
163
|
-
timeout: 10000,
|
|
164
|
-
|
|
165
|
-
onload: function() {
|
|
166
|
-
if (this.status === 200) {
|
|
167
|
-
try {
|
|
168
|
-
resolve(JSON.parse(this.responseText))
|
|
169
|
-
} catch (e) {
|
|
170
|
-
reject(new Error('Invalid JSON response'))
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
reject(new Error(`HTTP ${this.status}`))
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
onerror: function(e) {
|
|
178
|
-
// Log security events
|
|
179
|
-
if (this.status === 401 || this.status === 403) {
|
|
180
|
-
Ti.API.warn(`[SECURITY] Unauthorized: ${endpoint}`)
|
|
181
|
-
}
|
|
182
|
-
reject(e)
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
client.open(method, `${baseUrl}${endpoint}`)
|
|
187
|
-
|
|
188
|
-
// Security headers
|
|
189
|
-
client.setRequestHeader('User-Agent', `MyApp/${Ti.App.version}`)
|
|
190
|
-
client.setRequestHeader('Accept', 'application/json')
|
|
191
|
-
client.setRequestHeader('Content-Type', 'application/json')
|
|
192
|
-
|
|
193
|
-
client.send(data ? JSON.stringify(data) : null)
|
|
194
|
-
})
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
get(endpoint) {
|
|
198
|
-
return this.request('GET', endpoint)
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
post(endpoint, data) {
|
|
202
|
-
return this.request('POST', endpoint, data)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## Authentication Token Refresh Pattern
|
|
209
|
-
|
|
210
|
-
```javascript
|
|
211
|
-
// lib/services/authService.js
|
|
212
|
-
const { TokenStorage } = require('lib/services/tokenStorage')
|
|
213
|
-
|
|
214
|
-
const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000 // 5 minutes before expiry
|
|
215
|
-
|
|
216
|
-
exports.refreshAuthToken = async function() {
|
|
217
|
-
const refreshToken = TokenStorage.get('refreshToken')
|
|
218
|
-
|
|
219
|
-
const response = await api.post('/auth/refresh', {
|
|
220
|
-
refresh_token: refreshToken
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
TokenStorage.save(response.access_token)
|
|
224
|
-
|
|
225
|
-
// Set up auto-refresh
|
|
226
|
-
scheduleTokenRefresh(response.expires_in)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function scheduleTokenRefresh(expiresIn) {
|
|
230
|
-
const refreshTime = expiresIn - TOKEN_REFRESH_THRESHOLD
|
|
231
|
-
|
|
232
|
-
setTimeout(() => {
|
|
233
|
-
refreshAuthToken().catch(() => {
|
|
234
|
-
// Refresh failed - redirect to login
|
|
235
|
-
Alloy.createController('login').getView().open()
|
|
236
|
-
})
|
|
237
|
-
}, refreshTime)
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Input Validation
|
|
242
|
-
|
|
243
|
-
```javascript
|
|
244
|
-
// lib/services/validator.js
|
|
245
|
-
exports.Validator = {
|
|
246
|
-
email(email) {
|
|
247
|
-
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
248
|
-
if (!regex.test(email)) {
|
|
249
|
-
throw new ValidationError('Invalid email format')
|
|
250
|
-
}
|
|
251
|
-
return email.trim().toLowerCase()
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
password(password) {
|
|
255
|
-
if (password.length < 8) {
|
|
256
|
-
throw new ValidationError('Password must be at least 8 characters')
|
|
257
|
-
}
|
|
258
|
-
// Add more rules as needed
|
|
259
|
-
return password
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
sanitizeInput(input) {
|
|
263
|
-
// Remove potentially dangerous characters
|
|
264
|
-
return input
|
|
265
|
-
.replace(/[<>\"']/g, '')
|
|
266
|
-
.trim()
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
## OWASP Mobile Security Checklist
|
|
272
|
-
|
|
273
|
-
| Category | Check | Implementation |
|
|
274
|
-
| -------------------- | --------------------------- | ---------------------------------- |
|
|
275
|
-
| **Data Storage** | Credentials stored securely | Keychain/KeyStore for tokens |
|
|
276
|
-
| **Data Storage** | Sensitive data encrypted | AES-256 for cached data |
|
|
277
|
-
| **Communication** | HTTPS only | `validatesSecureCertificate: true` |
|
|
278
|
-
| **Communication** | Certificate pinning | SSL pinning enabled |
|
|
279
|
-
| **Authentication** | Token refresh | Auto-refresh before expiry |
|
|
280
|
-
| **Authentication** | Session timeout | Auto-logout after inactivity |
|
|
281
|
-
| **Input Validation** | Server-side validation | Never trust client input |
|
|
282
|
-
| **Input Validation** | Sanitize user input | Remove XSS patterns |
|
|
283
|
-
| **Cryptography** | No hardcoded keys | Keys from secure storage |
|
|
284
|
-
| **Cryptography** | Use standard algorithms | AES-256, SHA-256 |
|
|
1
|
+
# Device Security: Biometrics, Deep Links & Integrity
|
|
285
2
|
|
|
286
3
|
## Biometric Authentication
|
|
287
4
|
|
|
@@ -366,13 +83,13 @@ exports.BiometricService = {
|
|
|
366
83
|
}
|
|
367
84
|
```
|
|
368
85
|
|
|
369
|
-
|
|
86
|
+
## Biometric Login Flow
|
|
370
87
|
|
|
371
88
|
```javascript
|
|
372
89
|
// controllers/auth/login.js
|
|
373
|
-
const { BiometricService } = require('
|
|
374
|
-
const { TokenStorage } = require('
|
|
375
|
-
const { AuthService } = require('
|
|
90
|
+
const { BiometricService } = require('services/biometricService')
|
|
91
|
+
const { TokenStorage } = require('services/tokenStorage')
|
|
92
|
+
const { AuthService } = require('services/authService')
|
|
376
93
|
|
|
377
94
|
function init() {
|
|
378
95
|
// Check if biometric login is available and enabled
|
|
@@ -437,7 +154,7 @@ async function onLoginSuccess(user) {
|
|
|
437
154
|
|
|
438
155
|
```javascript
|
|
439
156
|
// lib/services/deepLinkService.js
|
|
440
|
-
const logger = require('
|
|
157
|
+
const logger = require('services/logger')
|
|
441
158
|
|
|
442
159
|
// Whitelist of allowed schemes and hosts
|
|
443
160
|
const ALLOWED_SCHEMES = ['myapp', 'https']
|
|
@@ -594,11 +311,11 @@ exports.DeepLinkService = {
|
|
|
594
311
|
}
|
|
595
312
|
```
|
|
596
313
|
|
|
597
|
-
|
|
314
|
+
## Registering Deep Link Handler
|
|
598
315
|
|
|
599
316
|
```javascript
|
|
600
317
|
// alloy.js
|
|
601
|
-
const { DeepLinkService } = require('
|
|
318
|
+
const { DeepLinkService } = require('services/deepLinkService')
|
|
602
319
|
|
|
603
320
|
// iOS: Handle app launch from deep link
|
|
604
321
|
if (Ti.App.getArguments().url) {
|
|
@@ -635,7 +352,7 @@ if (OS_ANDROID) {
|
|
|
635
352
|
|
|
636
353
|
```javascript
|
|
637
354
|
// lib/services/securityService.js
|
|
638
|
-
const logger = require('
|
|
355
|
+
const logger = require('services/logger')
|
|
639
356
|
|
|
640
357
|
exports.SecurityService = {
|
|
641
358
|
/**
|
|
@@ -811,11 +528,11 @@ exports.SecurityService = {
|
|
|
811
528
|
}
|
|
812
529
|
```
|
|
813
530
|
|
|
814
|
-
|
|
531
|
+
## Integrating Security Checks
|
|
815
532
|
|
|
816
533
|
```javascript
|
|
817
534
|
// alloy.js
|
|
818
|
-
const { SecurityService } = require('
|
|
535
|
+
const { SecurityService } = require('services/securityService')
|
|
819
536
|
|
|
820
537
|
// Check device integrity at app start
|
|
821
538
|
const securityCheck = SecurityService.enforceSecurityPolicy()
|
|
@@ -831,7 +548,7 @@ setInterval(() => {
|
|
|
831
548
|
}, 5 * 60 * 1000) // Every 5 minutes
|
|
832
549
|
```
|
|
833
550
|
|
|
834
|
-
##
|
|
551
|
+
## Security Checklist
|
|
835
552
|
|
|
836
553
|
| Category | Check | Implementation |
|
|
837
554
|
| -------------- | -------------------------- | ------------------------ |
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Security Fundamentals for Titanium Mobile Apps
|
|
2
|
+
|
|
3
|
+
## Token Storage Strategy
|
|
4
|
+
|
|
5
|
+
**NEVER store tokens in:** `Ti.App.Properties` (plaintext), localStorage, or files.
|
|
6
|
+
|
|
7
|
+
**USE platform-specific secure storage:**
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// lib/services/tokenStorage.js
|
|
11
|
+
exports.TokenStorage = {
|
|
12
|
+
save(token) {
|
|
13
|
+
if (Ti.Platform.osname === 'android') {
|
|
14
|
+
// Use Android KeyStore
|
|
15
|
+
const keyStore = Ti.Android.createKeyStore({
|
|
16
|
+
name: 'SecureKeyStore'
|
|
17
|
+
})
|
|
18
|
+
keyStore.addEntry('authToken', token)
|
|
19
|
+
} else {
|
|
20
|
+
// Use iOS Keychain
|
|
21
|
+
Ti.KeychainItem.setItem({
|
|
22
|
+
identifier: 'authToken',
|
|
23
|
+
value: token,
|
|
24
|
+
accessGroup: 'com.yourapp.keychain'
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
get() {
|
|
30
|
+
if (Ti.Platform.osname === 'android') {
|
|
31
|
+
const keyStore = Ti.Android.createKeyStore({
|
|
32
|
+
name: 'SecureKeyStore'
|
|
33
|
+
})
|
|
34
|
+
return keyStore.getEntry('authToken')
|
|
35
|
+
} else {
|
|
36
|
+
return Ti.KeychainItem.getItem({
|
|
37
|
+
identifier: 'authToken',
|
|
38
|
+
accessGroup: 'com.yourapp.keychain'
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
clear() {
|
|
44
|
+
if (Ti.Platform.osname === 'android') {
|
|
45
|
+
const keyStore = Ti.Android.createKeyStore({
|
|
46
|
+
name: 'SecureKeyStore'
|
|
47
|
+
})
|
|
48
|
+
keyStore.removeEntry('authToken')
|
|
49
|
+
} else {
|
|
50
|
+
Ti.KeychainItem.removeItem({
|
|
51
|
+
identifier: 'authToken',
|
|
52
|
+
accessGroup: 'com.yourapp.keychain'
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Certificate Pinning
|
|
60
|
+
|
|
61
|
+
Prevent man-in-the-middle attacks by pinning SSL certificates:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// lib/api/pinnedClient.js
|
|
65
|
+
exports.createPinnedClient = function() {
|
|
66
|
+
const client = Ti.Network.createHTTPClient({
|
|
67
|
+
// Security: Enable certificate pinning
|
|
68
|
+
certificatePinning: true,
|
|
69
|
+
|
|
70
|
+
// Specify allowed certificates
|
|
71
|
+
validatesSecureCertificate: true,
|
|
72
|
+
|
|
73
|
+
onload: () => {
|
|
74
|
+
// Success
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
onerror: (e) => {
|
|
78
|
+
// Certificate validation failed
|
|
79
|
+
if (e.error.indexOf('certificate') >= 0) {
|
|
80
|
+
Ti.API.error('Certificate pinning failed - possible MITM attack')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return client
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Add certificates to tiapp.xml:**
|
|
90
|
+
|
|
91
|
+
```xml
|
|
92
|
+
<ti:app>
|
|
93
|
+
<certificates>
|
|
94
|
+
<certificate>
|
|
95
|
+
<name>api.example.com</name>
|
|
96
|
+
<type>rsa</type>
|
|
97
|
+
<file>certificates/api-pin.pem</file>
|
|
98
|
+
</certificate>
|
|
99
|
+
</certificates>
|
|
100
|
+
</ti:app>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Data Encryption at Rest
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// lib/services/encryption.js
|
|
107
|
+
// AES-256 encryption for sensitive local data
|
|
108
|
+
|
|
109
|
+
const crypto = require('ti.crypto')
|
|
110
|
+
|
|
111
|
+
exports.encrypt = function(data, key) {
|
|
112
|
+
return crypto.encrypt({
|
|
113
|
+
data: data,
|
|
114
|
+
key: key,
|
|
115
|
+
algorithm: crypto.AES_256_CBC,
|
|
116
|
+
options: { mode: crypto.CBC }
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exports.decrypt = function(encryptedData, key) {
|
|
121
|
+
return crypto.decrypt({
|
|
122
|
+
data: encryptedData,
|
|
123
|
+
key: key,
|
|
124
|
+
algorithm: crypto.AES_256_CBC,
|
|
125
|
+
options: { mode: crypto.CBC }
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Usage: Secure cache of sensitive user data
|
|
130
|
+
module.exports = class SecureCache {
|
|
131
|
+
constructor(encryptionKey) {
|
|
132
|
+
this.key = encryptionKey
|
|
133
|
+
this.cache = {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
set(key, value) {
|
|
137
|
+
const encrypted = encrypt(JSON.stringify(value), this.key)
|
|
138
|
+
this.cache[key] = encrypted
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get(key) {
|
|
142
|
+
if (!this.cache[key]) return null
|
|
143
|
+
|
|
144
|
+
const decrypted = decrypt(this.cache[key], this.key)
|
|
145
|
+
return JSON.parse(decrypted)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
clear() {
|
|
149
|
+
this.cache = {}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Secure HTTP Communication
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
// lib/api/secureClient.js
|
|
158
|
+
exports.createSecureClient = function(baseUrl) {
|
|
159
|
+
return {
|
|
160
|
+
request(method, endpoint, data = null) {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const client = Ti.Network.createHTTPClient({
|
|
163
|
+
timeout: 10000,
|
|
164
|
+
|
|
165
|
+
onload: function() {
|
|
166
|
+
if (this.status === 200) {
|
|
167
|
+
try {
|
|
168
|
+
resolve(JSON.parse(this.responseText))
|
|
169
|
+
} catch (e) {
|
|
170
|
+
reject(new Error('Invalid JSON response'))
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
reject(new Error(`HTTP ${this.status}`))
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
onerror: function(e) {
|
|
178
|
+
// Log security events
|
|
179
|
+
if (this.status === 401 || this.status === 403) {
|
|
180
|
+
Ti.API.warn(`[SECURITY] Unauthorized: ${endpoint}`)
|
|
181
|
+
}
|
|
182
|
+
reject(e)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
client.open(method, `${baseUrl}${endpoint}`)
|
|
187
|
+
|
|
188
|
+
// Security headers
|
|
189
|
+
client.setRequestHeader('User-Agent', `MyApp/${Ti.App.version}`)
|
|
190
|
+
client.setRequestHeader('Accept', 'application/json')
|
|
191
|
+
client.setRequestHeader('Content-Type', 'application/json')
|
|
192
|
+
|
|
193
|
+
client.send(data ? JSON.stringify(data) : null)
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
get(endpoint) {
|
|
198
|
+
return this.request('GET', endpoint)
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
post(endpoint, data) {
|
|
202
|
+
return this.request('POST', endpoint, data)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Authentication Token Refresh Pattern
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
// lib/services/authService.js
|
|
212
|
+
const { TokenStorage } = require('services/tokenStorage')
|
|
213
|
+
|
|
214
|
+
const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000 // 5 minutes before expiry
|
|
215
|
+
|
|
216
|
+
exports.refreshAuthToken = async function() {
|
|
217
|
+
const refreshToken = TokenStorage.get('refreshToken')
|
|
218
|
+
|
|
219
|
+
const response = await api.post('/auth/refresh', {
|
|
220
|
+
refresh_token: refreshToken
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
TokenStorage.save(response.access_token)
|
|
224
|
+
|
|
225
|
+
// Set up auto-refresh
|
|
226
|
+
scheduleTokenRefresh(response.expires_in)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function scheduleTokenRefresh(expiresIn) {
|
|
230
|
+
const refreshTime = expiresIn - TOKEN_REFRESH_THRESHOLD
|
|
231
|
+
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
refreshAuthToken().catch(() => {
|
|
234
|
+
// Refresh failed - redirect to login
|
|
235
|
+
Alloy.createController('login').getView().open()
|
|
236
|
+
})
|
|
237
|
+
}, refreshTime)
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Input Validation
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
// lib/services/validator.js
|
|
245
|
+
exports.Validator = {
|
|
246
|
+
email(email) {
|
|
247
|
+
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
248
|
+
if (!regex.test(email)) {
|
|
249
|
+
throw new ValidationError('Invalid email format')
|
|
250
|
+
}
|
|
251
|
+
return email.trim().toLowerCase()
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
password(password) {
|
|
255
|
+
if (password.length < 8) {
|
|
256
|
+
throw new ValidationError('Password must be at least 8 characters')
|
|
257
|
+
}
|
|
258
|
+
// Add more rules as needed
|
|
259
|
+
return password
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
sanitizeInput(input) {
|
|
263
|
+
// Remove potentially dangerous characters
|
|
264
|
+
return input
|
|
265
|
+
.replace(/[<>\"']/g, '')
|
|
266
|
+
.trim()
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## OWASP Mobile Security Checklist
|
|
272
|
+
|
|
273
|
+
| Category | Check | Implementation |
|
|
274
|
+
| -------------------- | --------------------------- | ---------------------------------- |
|
|
275
|
+
| **Data Storage** | Credentials stored securely | Keychain/KeyStore for tokens |
|
|
276
|
+
| **Data Storage** | Sensitive data encrypted | AES-256 for cached data |
|
|
277
|
+
| **Communication** | HTTPS only | `validatesSecureCertificate: true` |
|
|
278
|
+
| **Communication** | Certificate pinning | SSL pinning enabled |
|
|
279
|
+
| **Authentication** | Token refresh | Auto-refresh before expiry |
|
|
280
|
+
| **Authentication** | Session timeout | Auto-logout after inactivity |
|
|
281
|
+
| **Input Validation** | Server-side validation | Never trust client input |
|
|
282
|
+
| **Input Validation** | Sanitize user input | Remove XSS patterns |
|
|
283
|
+
| **Cryptography** | No hardcoded keys | Keys from secure storage |
|
|
284
|
+
| **Cryptography** | Use standard algorithms | AES-256, SHA-256 |
|
|
@@ -82,8 +82,8 @@ exports.appStore = new StateStore()
|
|
|
82
82
|
|
|
83
83
|
```javascript
|
|
84
84
|
// alloy.js
|
|
85
|
-
const { appStore } = require('
|
|
86
|
-
const { TokenStorage } = require('
|
|
85
|
+
const { appStore } = require('services/stateStore')
|
|
86
|
+
const { TokenStorage } = require('services/tokenStorage')
|
|
87
87
|
|
|
88
88
|
// Initialize store from persisted data
|
|
89
89
|
function initAppStore() {
|
|
@@ -105,7 +105,7 @@ initAppStore()
|
|
|
105
105
|
|
|
106
106
|
```javascript
|
|
107
107
|
// controllers/home/index.js
|
|
108
|
-
const { appStore } = require('
|
|
108
|
+
const { appStore } = require('services/stateStore')
|
|
109
109
|
|
|
110
110
|
function init() {
|
|
111
111
|
// Get current state
|
|
@@ -143,9 +143,9 @@ function cleanup() {
|
|
|
143
143
|
|
|
144
144
|
```javascript
|
|
145
145
|
// lib/services/authService.js
|
|
146
|
-
const { appStore } = require('
|
|
147
|
-
const { TokenStorage } = require('
|
|
148
|
-
const api = require('
|
|
146
|
+
const { appStore } = require('services/stateStore')
|
|
147
|
+
const { TokenStorage } = require('services/tokenStorage')
|
|
148
|
+
const api = require('api/authApi')
|
|
149
149
|
|
|
150
150
|
exports.login = async function(email, password) {
|
|
151
151
|
appStore.setState({
|
|
@@ -195,7 +195,7 @@ Services can maintain their own cached state:
|
|
|
195
195
|
|
|
196
196
|
```javascript
|
|
197
197
|
// lib/services/userService.js
|
|
198
|
-
const { appStore } = require('
|
|
198
|
+
const { appStore } = require('services/stateStore')
|
|
199
199
|
|
|
200
200
|
class UserService {
|
|
201
201
|
constructor() {
|
|
@@ -260,7 +260,7 @@ When state needs to sync with server:
|
|
|
260
260
|
|
|
261
261
|
```javascript
|
|
262
262
|
// lib/services/syncService.js
|
|
263
|
-
const { appStore } = require('
|
|
263
|
+
const { appStore } = require('services/stateStore')
|
|
264
264
|
|
|
265
265
|
exports.syncWithServer = function() {
|
|
266
266
|
const localState = appStore.getState()
|
|
@@ -727,8 +727,8 @@ appStore.use(debugMiddleware)
|
|
|
727
727
|
|
|
728
728
|
```javascript
|
|
729
729
|
// controllers/debug/stateViewer.js (Development only)
|
|
730
|
-
const { StateDebug } = require('
|
|
731
|
-
const { appStore } = require('
|
|
730
|
+
const { StateDebug } = require('services/stateDebug')
|
|
731
|
+
const { appStore } = require('services/stateStore')
|
|
732
732
|
|
|
733
733
|
function init() {
|
|
734
734
|
if (!Alloy.CFG.debug) {
|