add Touch ID wallet unlock via Swift biometric helper + Bun spawn
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
getBinaryDir,
|
||||
MoneroWalletManager,
|
||||
} from './lib/monero'
|
||||
import { canPromptTouchID, promptTouchID } from './lib/biometric'
|
||||
|
||||
const env = getENV()
|
||||
|
||||
@@ -133,6 +134,14 @@ type TxEntry = {
|
||||
type RPC = {
|
||||
bun: RPCSchema<{
|
||||
requests: {
|
||||
biometricCanAuth: {
|
||||
params: {}
|
||||
response: boolean
|
||||
}
|
||||
biometricAuth: {
|
||||
params: { reason: string }
|
||||
response: boolean
|
||||
}
|
||||
getSecret: {
|
||||
params: {
|
||||
service: string
|
||||
@@ -357,6 +366,20 @@ const rpc = BrowserView.defineRPC<RPC>({
|
||||
maxRequestTime: 120000,
|
||||
handlers: {
|
||||
requests: {
|
||||
biometricCanAuth: async () => {
|
||||
try {
|
||||
return canPromptTouchID()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
biometricAuth: async ({ reason }) => {
|
||||
try {
|
||||
return promptTouchID(reason)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
getSecret: async ({ name, service }) => {
|
||||
return await Bun.secrets.get({ name, service })
|
||||
},
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { existsSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { PATHS } from 'electrobun/bun'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
function findHelper(): string {
|
||||
const candidates = [
|
||||
join(__dirname, '..', 'native', 'biometric-helper'),
|
||||
join(PATHS.RESOURCES_FOLDER, 'native', 'biometric-helper'),
|
||||
]
|
||||
for (const p of candidates) {
|
||||
if (existsSync(p)) return p
|
||||
}
|
||||
throw new Error(
|
||||
`biometric-helper not found at any of:\n${candidates.join('\n')}`,
|
||||
)
|
||||
}
|
||||
|
||||
const HELPER_PATH = findHelper()
|
||||
|
||||
export function canPromptTouchID(): boolean {
|
||||
const result = Bun.spawnSync([HELPER_PATH, '--check'])
|
||||
return result.exitCode === 0
|
||||
}
|
||||
|
||||
export function promptTouchID(reason: string): boolean {
|
||||
const result = Bun.spawnSync([HELPER_PATH, reason])
|
||||
return result.exitCode === 0
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
import LocalAuthentication
|
||||
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
let available = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
|
||||
|
||||
let isCheck = CommandLine.arguments.contains("--check")
|
||||
if isCheck {
|
||||
print(available)
|
||||
exit(available ? 0 : 1)
|
||||
}
|
||||
|
||||
guard available else {
|
||||
print(error!.localizedDescription)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let reason = CommandLine.arguments.last ?? "Authenticate"
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
|
||||
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authError in
|
||||
if success {
|
||||
exit(0)
|
||||
}
|
||||
if let err = authError {
|
||||
print(err.localizedDescription)
|
||||
}
|
||||
exit(1)
|
||||
}
|
||||
|
||||
_ = sem.wait(timeout: .distantFuture)
|
||||
Reference in New Issue
Block a user