pakstr 0.31.0 → 1.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/README.md +1 -1
- package/android-template/app/build.gradle.kts +1 -0
- package/android-template/app/src/androidTest/java/com/pakstr/app/PakstrWebBridgeTest.kt +153 -0
- package/android-template/app/src/main/assets/nip07.js +124 -74
- package/android-template/app/src/main/java/com/pakstr/app/AppOrigin.kt +77 -0
- package/android-template/app/src/main/java/com/pakstr/app/PakstrBridge.kt +5 -5
- package/android-template/app/src/main/java/com/pakstr/app/PakstrWebBridge.kt +89 -0
- package/android-template/app/src/main/java/com/pakstr/app/WebMessageRouter.kt +186 -0
- package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +65 -49
- package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +69 -489
- package/android-template/app/src/test/java/com/pakstr/app/AppOriginTest.kt +114 -0
- package/android-template/app/src/test/java/com/pakstr/app/WebMessageRouterTest.kt +279 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/NostrBridgeDispatchTest.kt +113 -0
- package/android-template/gradle/libs.versions.toml +2 -0
- package/package.json +1 -1
|
@@ -1,514 +1,90 @@
|
|
|
1
1
|
package com.pakstr.app.signer
|
|
2
2
|
|
|
3
|
-
import android.os.Handler
|
|
4
|
-
import android.os.Looper
|
|
5
|
-
import android.webkit.JavascriptInterface
|
|
6
|
-
import android.webkit.WebView
|
|
7
|
-
|
|
8
|
-
import com.pakstr.app.debug.AppDebugLogger
|
|
9
3
|
import com.pakstr.app.signer.exceptions.SignerUnavailableException
|
|
10
|
-
import
|
|
11
|
-
import kotlinx.coroutines.Dispatchers
|
|
12
|
-
import kotlinx.coroutines.SupervisorJob
|
|
13
|
-
import kotlinx.coroutines.launch
|
|
4
|
+
import com.pakstr.app.signer.interfaces.NostrSigner
|
|
14
5
|
import org.json.JSONObject
|
|
15
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Native side of `window.nostr`. WebMessageRouter calls [handle] for
|
|
9
|
+
* `nostr.*` messages that already passed its origin and frame checks.
|
|
10
|
+
*
|
|
11
|
+
* Results are returned as the signer produced them (signEvent returns the
|
|
12
|
+
* signed event JSON, which nip07.js parses). Failures are thrown and mapped
|
|
13
|
+
* to `{ error, message }` by [signerErrorResponse].
|
|
14
|
+
*/
|
|
16
15
|
class NostrBridge(
|
|
17
|
-
private val
|
|
18
|
-
private val
|
|
16
|
+
private val signerProvider: suspend () -> NostrSigner,
|
|
17
|
+
private val log: (String) -> Unit = {},
|
|
18
|
+
private val logError: (String, Throwable) -> Unit = { _, _ -> }
|
|
19
19
|
) {
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
private val scope = CoroutineScope(
|
|
24
|
-
SupervisorJob() + Dispatchers.IO
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
@JavascriptInterface
|
|
28
|
-
fun call(
|
|
21
|
+
suspend fun handle(
|
|
29
22
|
method: String,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"getPublicKey" -> {
|
|
45
|
-
|
|
46
|
-
scope.launch {
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
val pk =
|
|
50
|
-
signerManager
|
|
51
|
-
.getConfiguredSigner()
|
|
52
|
-
.getPublicKey()
|
|
53
|
-
resolve(
|
|
54
|
-
callbackId,
|
|
55
|
-
pk
|
|
56
|
-
)
|
|
57
|
-
} catch (e: SignerUnavailableException) {
|
|
58
|
-
AppDebugLogger.error(
|
|
59
|
-
|
|
60
|
-
webView.context,
|
|
61
|
-
|
|
62
|
-
"SIGNER",
|
|
63
|
-
|
|
64
|
-
"getPublicKey failed",
|
|
65
|
-
|
|
66
|
-
e
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
resolve(
|
|
70
|
-
callbackId,
|
|
71
|
-
errorResponse(
|
|
72
|
-
"SIGNER_UNAVAILABLE",
|
|
73
|
-
e.message ?: "Signer unavailable"
|
|
74
|
-
)
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
} catch (e: Exception) {
|
|
78
|
-
AppDebugLogger.error(
|
|
79
|
-
|
|
80
|
-
webView.context,
|
|
81
|
-
|
|
82
|
-
"SIGNER",
|
|
83
|
-
|
|
84
|
-
"getPublicKey failed",
|
|
85
|
-
|
|
86
|
-
e
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
resolve(
|
|
90
|
-
callbackId,
|
|
91
|
-
errorResponse(
|
|
92
|
-
"SIGNER_ERROR",
|
|
93
|
-
e.message ?: "Unknown signer error"
|
|
94
|
-
)
|
|
95
|
-
)
|
|
23
|
+
params: JSONObject?
|
|
24
|
+
): String {
|
|
25
|
+
if (method !in METHODS) {
|
|
26
|
+
log("Request: unsupported method")
|
|
27
|
+
throw UnsupportedBridgeMethodException()
|
|
28
|
+
}
|
|
29
|
+
log("Request: $method")
|
|
30
|
+
|
|
31
|
+
return try {
|
|
32
|
+
val result = when (method) {
|
|
33
|
+
"getPublicKey" -> signerProvider().getPublicKey()
|
|
34
|
+
"signEvent" -> {
|
|
35
|
+
val event = requireNotNull(params) {
|
|
36
|
+
"Event payload is empty"
|
|
96
37
|
}
|
|
38
|
+
signerProvider().signEvent(event.toString())
|
|
97
39
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
scope.launch {
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
require(!payload.isNullOrBlank()) {
|
|
105
|
-
"Event payload is empty"
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
val result =
|
|
109
|
-
signerManager
|
|
110
|
-
.getConfiguredSigner()
|
|
111
|
-
.signEvent(
|
|
112
|
-
payload
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
AppDebugLogger.log(
|
|
116
|
-
|
|
117
|
-
webView.context,
|
|
118
|
-
|
|
119
|
-
"SIGNER",
|
|
120
|
-
|
|
121
|
-
"signEvent success"
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
resolve(
|
|
126
|
-
callbackId,
|
|
127
|
-
result
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
} catch (e: SignerUnavailableException) {
|
|
131
|
-
resolve(
|
|
132
|
-
callbackId,
|
|
133
|
-
errorResponse(
|
|
134
|
-
"SIGNER_UNAVAILABLE",
|
|
135
|
-
e.message ?: "Signer unavailable"
|
|
136
|
-
)
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
} catch (e: Exception) {
|
|
140
|
-
AppDebugLogger.error(
|
|
141
|
-
|
|
142
|
-
webView.context,
|
|
143
|
-
|
|
144
|
-
"SIGNER",
|
|
145
|
-
|
|
146
|
-
"signEvent failed",
|
|
147
|
-
|
|
148
|
-
e
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
resolve(
|
|
152
|
-
callbackId,
|
|
153
|
-
errorResponse(
|
|
154
|
-
"SIGNER_ERROR",
|
|
155
|
-
e.message ?: "Unknown signer error"
|
|
156
|
-
)
|
|
157
|
-
)
|
|
40
|
+
"nip04_encrypt" -> {
|
|
41
|
+
val json = requireNotNull(params) {
|
|
42
|
+
"NIP04 encrypt payload is empty"
|
|
158
43
|
}
|
|
44
|
+
val pubkey = json.getString("pubkey")
|
|
45
|
+
val plaintext = json.getString("plaintext")
|
|
46
|
+
signerProvider().nip04Encrypt(pubkey, plaintext)
|
|
159
47
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
scope.launch {
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
|
|
168
|
-
require(!payload.isNullOrBlank()) {
|
|
169
|
-
"NIP04 encrypt payload is empty"
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
val json =
|
|
173
|
-
JSONObject(payload)
|
|
174
|
-
|
|
175
|
-
val pubkey =
|
|
176
|
-
json.getString("pubkey")
|
|
177
|
-
|
|
178
|
-
val plaintext =
|
|
179
|
-
json.getString("plaintext")
|
|
180
|
-
|
|
181
|
-
val result =
|
|
182
|
-
signerManager
|
|
183
|
-
.getConfiguredSigner()
|
|
184
|
-
.nip04Encrypt(
|
|
185
|
-
pubkey,
|
|
186
|
-
plaintext
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
AppDebugLogger.log(
|
|
190
|
-
|
|
191
|
-
webView.context,
|
|
192
|
-
|
|
193
|
-
"SIGNER",
|
|
194
|
-
|
|
195
|
-
"nip04_encrypt success"
|
|
196
|
-
|
|
197
|
-
)
|
|
198
|
-
resolve(
|
|
199
|
-
callbackId,
|
|
200
|
-
result
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
} catch (e: SignerUnavailableException) {
|
|
204
|
-
|
|
205
|
-
resolve(
|
|
206
|
-
callbackId,
|
|
207
|
-
errorResponse(
|
|
208
|
-
"SIGNER_UNAVAILABLE",
|
|
209
|
-
e.message ?: "Signer unavailable"
|
|
210
|
-
)
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
} catch (e: Exception) {
|
|
214
|
-
AppDebugLogger.error(
|
|
215
|
-
|
|
216
|
-
webView.context,
|
|
217
|
-
|
|
218
|
-
"SIGNER",
|
|
219
|
-
|
|
220
|
-
"nip04_encrypt failed",
|
|
221
|
-
|
|
222
|
-
e
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
resolve(
|
|
226
|
-
callbackId,
|
|
227
|
-
errorResponse(
|
|
228
|
-
"SIGNER_ERROR",
|
|
229
|
-
e.message ?: "Unknown signer error"
|
|
230
|
-
)
|
|
231
|
-
)
|
|
48
|
+
"nip04_decrypt" -> {
|
|
49
|
+
val json = requireNotNull(params) {
|
|
50
|
+
"NIP04 decrypt payload is empty"
|
|
232
51
|
}
|
|
52
|
+
val pubkey = json.getString("pubkey")
|
|
53
|
+
val ciphertext = json.getString("ciphertext")
|
|
54
|
+
signerProvider().nip04Decrypt(pubkey, ciphertext)
|
|
233
55
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
|
|
242
|
-
require(!payload.isNullOrBlank()) {
|
|
243
|
-
"NIP04 decrypt payload is empty"
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
val json =
|
|
247
|
-
JSONObject(payload)
|
|
248
|
-
|
|
249
|
-
val pubkey =
|
|
250
|
-
json.getString("pubkey")
|
|
251
|
-
|
|
252
|
-
val ciphertext =
|
|
253
|
-
json.getString("ciphertext")
|
|
254
|
-
|
|
255
|
-
val result =
|
|
256
|
-
signerManager
|
|
257
|
-
.getConfiguredSigner()
|
|
258
|
-
.nip04Decrypt(
|
|
259
|
-
pubkey,
|
|
260
|
-
ciphertext
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
AppDebugLogger.log(
|
|
264
|
-
|
|
265
|
-
webView.context,
|
|
266
|
-
|
|
267
|
-
"SIGNER",
|
|
268
|
-
|
|
269
|
-
"nip04_decrypt success"
|
|
270
|
-
|
|
271
|
-
)
|
|
272
|
-
resolve(
|
|
273
|
-
callbackId,
|
|
274
|
-
result
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
} catch (e: SignerUnavailableException) {
|
|
278
|
-
|
|
279
|
-
resolve(
|
|
280
|
-
callbackId,
|
|
281
|
-
errorResponse(
|
|
282
|
-
"SIGNER_UNAVAILABLE",
|
|
283
|
-
e.message ?: "Signer unavailable"
|
|
284
|
-
)
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
} catch (e: Exception) {
|
|
288
|
-
AppDebugLogger.error(
|
|
289
|
-
|
|
290
|
-
webView.context,
|
|
291
|
-
|
|
292
|
-
"SIGNER",
|
|
293
|
-
|
|
294
|
-
"nip04_decrypt failed",
|
|
295
|
-
|
|
296
|
-
e
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
resolve(
|
|
300
|
-
callbackId,
|
|
301
|
-
errorResponse(
|
|
302
|
-
"SIGNER_ERROR",
|
|
303
|
-
e.message ?: "Unknown signer error"
|
|
304
|
-
)
|
|
305
|
-
)
|
|
306
|
-
}
|
|
56
|
+
"nip44_encrypt" -> {
|
|
57
|
+
val json = params ?: JSONObject()
|
|
58
|
+
val pubkey = json.getString("pubkey")
|
|
59
|
+
val plaintext = json.getString("plaintext")
|
|
60
|
+
signerProvider().nip44Encrypt(pubkey, plaintext)
|
|
307
61
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
try {
|
|
315
|
-
|
|
316
|
-
val json =
|
|
317
|
-
JSONObject(payload ?: "")
|
|
318
|
-
|
|
319
|
-
val result =
|
|
320
|
-
signerManager
|
|
321
|
-
.getConfiguredSigner()
|
|
322
|
-
.nip44Encrypt(
|
|
323
|
-
json.getString("pubkey"),
|
|
324
|
-
json.getString("plaintext")
|
|
325
|
-
)
|
|
326
|
-
AppDebugLogger.log(
|
|
327
|
-
|
|
328
|
-
webView.context,
|
|
329
|
-
|
|
330
|
-
"SIGNER",
|
|
331
|
-
|
|
332
|
-
"nip44_encrypt success"
|
|
333
|
-
|
|
334
|
-
)
|
|
335
|
-
resolve(
|
|
336
|
-
callbackId,
|
|
337
|
-
result
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
} catch (e: SignerUnavailableException) {
|
|
341
|
-
resolve(
|
|
342
|
-
callbackId,
|
|
343
|
-
signerErrorResponse(e)
|
|
344
|
-
)
|
|
345
|
-
} catch (e: Exception) {
|
|
346
|
-
AppDebugLogger.error(
|
|
347
|
-
|
|
348
|
-
webView.context,
|
|
349
|
-
|
|
350
|
-
"SIGNER",
|
|
351
|
-
|
|
352
|
-
"nip44_encrypt failed",
|
|
353
|
-
|
|
354
|
-
e
|
|
355
|
-
|
|
356
|
-
)
|
|
357
|
-
resolve(
|
|
358
|
-
callbackId,
|
|
359
|
-
signerErrorResponse(e)
|
|
360
|
-
)
|
|
361
|
-
}
|
|
62
|
+
"nip44_decrypt" -> {
|
|
63
|
+
val json = params ?: JSONObject()
|
|
64
|
+
val pubkey = json.getString("pubkey")
|
|
65
|
+
val ciphertext = json.getString("ciphertext")
|
|
66
|
+
signerProvider().nip44Decrypt(pubkey, ciphertext)
|
|
362
67
|
}
|
|
68
|
+
else -> throw UnsupportedBridgeMethodException()
|
|
363
69
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
|
|
371
|
-
val json =
|
|
372
|
-
JSONObject(payload ?: "")
|
|
373
|
-
|
|
374
|
-
val result =
|
|
375
|
-
signerManager
|
|
376
|
-
.getConfiguredSigner()
|
|
377
|
-
.nip44Decrypt(
|
|
378
|
-
json.getString("pubkey"),
|
|
379
|
-
json.getString("ciphertext")
|
|
380
|
-
)
|
|
381
|
-
AppDebugLogger.log(
|
|
382
|
-
|
|
383
|
-
webView.context,
|
|
384
|
-
|
|
385
|
-
"SIGNER",
|
|
386
|
-
|
|
387
|
-
"nip44_decrypt success"
|
|
388
|
-
|
|
389
|
-
)
|
|
390
|
-
resolve(
|
|
391
|
-
callbackId,
|
|
392
|
-
result
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
} catch (e: SignerUnavailableException) {
|
|
396
|
-
resolve(
|
|
397
|
-
callbackId,
|
|
398
|
-
signerErrorResponse(e)
|
|
399
|
-
)
|
|
400
|
-
} catch (e: Exception) {
|
|
401
|
-
AppDebugLogger.error(
|
|
402
|
-
|
|
403
|
-
webView.context,
|
|
404
|
-
|
|
405
|
-
"SIGNER",
|
|
406
|
-
|
|
407
|
-
"nip44_decrypt failed",
|
|
408
|
-
|
|
409
|
-
e
|
|
410
|
-
|
|
411
|
-
)
|
|
412
|
-
resolve(
|
|
413
|
-
callbackId,
|
|
414
|
-
signerErrorResponse(e)
|
|
415
|
-
)
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
else -> {
|
|
421
|
-
|
|
422
|
-
resolve(
|
|
423
|
-
callbackId,
|
|
424
|
-
""
|
|
425
|
-
)
|
|
426
|
-
}
|
|
70
|
+
log("$method success")
|
|
71
|
+
result
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
logError("$method failed", e)
|
|
74
|
+
throw e
|
|
427
75
|
}
|
|
428
76
|
}
|
|
429
77
|
|
|
430
|
-
@JavascriptInterface
|
|
431
|
-
fun debugLog(
|
|
432
|
-
level: String,
|
|
433
|
-
message: String
|
|
434
|
-
) {
|
|
435
|
-
|
|
436
|
-
when (level.uppercase()) {
|
|
437
|
-
|
|
438
|
-
"ERROR" -> {
|
|
439
|
-
|
|
440
|
-
AppDebugLogger.error(
|
|
441
|
-
webView.context,
|
|
442
|
-
"WEB-JS",
|
|
443
|
-
message
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
"WARN" -> {
|
|
449
|
-
|
|
450
|
-
AppDebugLogger.log(
|
|
451
|
-
webView.context,
|
|
452
|
-
"WEB-WARN",
|
|
453
|
-
message
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
else -> {
|
|
459
|
-
|
|
460
|
-
AppDebugLogger.log(
|
|
461
|
-
webView.context,
|
|
462
|
-
"WEB",
|
|
463
|
-
message
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
private fun resolve(
|
|
471
|
-
callbackId: String,
|
|
472
|
-
result: String
|
|
473
|
-
) {
|
|
474
|
-
|
|
475
|
-
mainHandler.post {
|
|
476
|
-
|
|
477
|
-
val js =
|
|
478
|
-
"""
|
|
479
|
-
window.__nostrResolve(
|
|
480
|
-
${JSONObject.quote(callbackId)},
|
|
481
|
-
${JSONObject.quote(result)}
|
|
482
|
-
);
|
|
483
|
-
""".trimIndent()
|
|
484
|
-
|
|
485
|
-
webView.evaluateJavascript(js, null)
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
private fun errorResponse(
|
|
490
|
-
code: String,
|
|
491
|
-
message: String
|
|
492
|
-
): String {
|
|
493
|
-
|
|
494
|
-
return JSONObject()
|
|
495
|
-
.apply {
|
|
496
|
-
|
|
497
|
-
put(
|
|
498
|
-
"error",
|
|
499
|
-
code
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
put(
|
|
503
|
-
"message",
|
|
504
|
-
message
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
.toString()
|
|
509
|
-
}
|
|
510
|
-
|
|
511
78
|
companion object {
|
|
79
|
+
private val METHODS = setOf(
|
|
80
|
+
"getPublicKey",
|
|
81
|
+
"signEvent",
|
|
82
|
+
"nip04_encrypt",
|
|
83
|
+
"nip04_decrypt",
|
|
84
|
+
"nip44_encrypt",
|
|
85
|
+
"nip44_decrypt"
|
|
86
|
+
)
|
|
87
|
+
|
|
512
88
|
internal fun signerErrorResponse(error: Exception): String {
|
|
513
89
|
return JSONObject()
|
|
514
90
|
.put(
|
|
@@ -531,3 +107,7 @@ class NostrBridge(
|
|
|
531
107
|
}
|
|
532
108
|
}
|
|
533
109
|
}
|
|
110
|
+
|
|
111
|
+
class UnsupportedBridgeMethodException : IllegalArgumentException(
|
|
112
|
+
"Unsupported method"
|
|
113
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
package com.pakstr.app
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Assert.assertFalse
|
|
5
|
+
import org.junit.Assert.assertTrue
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
|
|
8
|
+
class AppOriginTest {
|
|
9
|
+
private val port = Config.PORT
|
|
10
|
+
|
|
11
|
+
@Test
|
|
12
|
+
fun originRuleIsTheExactLocalServerOrigin() {
|
|
13
|
+
assertEquals("http://127.0.0.1:$port", AppOrigin.ORIGIN)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun onlyTheExactAppOriginIsTrusted() {
|
|
18
|
+
assertTrue(AppOrigin.isTrustedOrigin("http", "127.0.0.1", port, null, null))
|
|
19
|
+
assertTrue(AppOrigin.isTrustedOrigin("http", "127.0.0.1", port, null, ""))
|
|
20
|
+
assertTrue(AppOrigin.isTrustedOrigin("http", "127.0.0.1", port, null, "/"))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
fun nearMissOriginsAreRejected() {
|
|
25
|
+
val rejected = listOf(
|
|
26
|
+
arrayOf<Any?>("https", "127.0.0.1", port, null, null),
|
|
27
|
+
arrayOf<Any?>("HTTP", "127.0.0.1", port, null, null),
|
|
28
|
+
arrayOf<Any?>("http", "127.0.0.1", port + 1, null, null),
|
|
29
|
+
arrayOf<Any?>("http", "127.0.0.1", -1, null, null),
|
|
30
|
+
arrayOf<Any?>("http", "127.0.0.1", 80, null, null),
|
|
31
|
+
arrayOf<Any?>("http", "localhost", port, null, null),
|
|
32
|
+
arrayOf<Any?>("http", "127.0.0.2", port, null, null),
|
|
33
|
+
arrayOf<Any?>("http", "[::1]", port, null, null),
|
|
34
|
+
arrayOf<Any?>("http", "127.0.0.1.example.com", port, null, null),
|
|
35
|
+
arrayOf<Any?>("http", "example.com", port, null, null),
|
|
36
|
+
arrayOf<Any?>("http", "127.0.0.1", port, "user", null),
|
|
37
|
+
arrayOf<Any?>("http", "127.0.0.1", port, null, "/index.html"),
|
|
38
|
+
arrayOf<Any?>("file", null, -1, null, "/android_asset/www/index.html"),
|
|
39
|
+
arrayOf<Any?>("data", null, -1, null, "text/html,x"),
|
|
40
|
+
arrayOf<Any?>(null, null, -1, null, null)
|
|
41
|
+
)
|
|
42
|
+
for (origin in rejected) {
|
|
43
|
+
assertFalse(
|
|
44
|
+
origin.contentToString(),
|
|
45
|
+
AppOrigin.isTrustedOrigin(
|
|
46
|
+
origin[0] as String?,
|
|
47
|
+
origin[1] as String?,
|
|
48
|
+
origin[2] as Int,
|
|
49
|
+
origin[3] as String?,
|
|
50
|
+
origin[4] as String?
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test
|
|
57
|
+
fun mainFrameNavigationStaysOnTheAppOrigin() {
|
|
58
|
+
assertEquals(
|
|
59
|
+
NavigationDecision.ALLOW,
|
|
60
|
+
navigationDecision("http", "127.0.0.1", port, null, isForMainFrame = true)
|
|
61
|
+
)
|
|
62
|
+
assertEquals(
|
|
63
|
+
NavigationDecision.ALLOW,
|
|
64
|
+
navigationDecision("http", "127.0.0.1", port, null, isForMainFrame = false)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
fun navigationOffTheAppOriginIsBlocked() {
|
|
70
|
+
val blocked = listOf(
|
|
71
|
+
Triple("http", "127.0.0.1", port + 1),
|
|
72
|
+
Triple("http", "127.0.0.1", -1),
|
|
73
|
+
Triple("https", "127.0.0.1", port),
|
|
74
|
+
Triple("http", "localhost", port),
|
|
75
|
+
Triple("https", "example.com", -1),
|
|
76
|
+
Triple("intent", null, -1),
|
|
77
|
+
Triple("javascript", null, -1),
|
|
78
|
+
Triple("file", null, -1),
|
|
79
|
+
Triple("about", null, -1),
|
|
80
|
+
Triple("data", null, -1)
|
|
81
|
+
)
|
|
82
|
+
for ((scheme, host, navigationPort) in blocked) {
|
|
83
|
+
assertEquals(
|
|
84
|
+
"$scheme://$host:$navigationPort",
|
|
85
|
+
NavigationDecision.BLOCK,
|
|
86
|
+
navigationDecision(scheme, host, navigationPort, null, isForMainFrame = true)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
assertEquals(
|
|
90
|
+
NavigationDecision.BLOCK,
|
|
91
|
+
navigationDecision("http", "127.0.0.1", port, "user:pass", isForMainFrame = true)
|
|
92
|
+
)
|
|
93
|
+
assertEquals(
|
|
94
|
+
NavigationDecision.BLOCK,
|
|
95
|
+
navigationDecision(null, null, -1, null, isForMainFrame = true)
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
fun nip55RequestsAreDispatchedOnlyFromTheMainFrame() {
|
|
101
|
+
assertEquals(
|
|
102
|
+
NavigationDecision.NIP55_SIGN_REQUEST,
|
|
103
|
+
navigationDecision("nostrsigner", null, -1, null, isForMainFrame = true)
|
|
104
|
+
)
|
|
105
|
+
assertEquals(
|
|
106
|
+
NavigationDecision.NIP55_SIGN_REQUEST,
|
|
107
|
+
navigationDecision("NostrSigner", null, -1, null, isForMainFrame = true)
|
|
108
|
+
)
|
|
109
|
+
assertEquals(
|
|
110
|
+
NavigationDecision.REJECT_SUBFRAME_NIP55,
|
|
111
|
+
navigationDecision("nostrsigner", null, -1, null, isForMainFrame = false)
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|