feat(mobile): add U2F support for Android devices (#2311)

* feat(mobile): add U2F support for Android devices

* chore: fix specs
This commit is contained in:
Karol Sójko
2023-04-17 14:57:41 +02:00
committed by GitHub
parent c0c9a4ff91
commit 725f55fca7
23 changed files with 449 additions and 86 deletions

View File

@@ -1,8 +1,13 @@
import { WebApplication } from '@/Application/Application'
import { Username } from '@standardnotes/snjs'
import { ChallengePrompt } from '@standardnotes/services'
import { RefObject, useState } from 'react'
import { WebApplication } from '@/Application/Application'
import { isAndroid } from '@/Utils'
import Button from '../Button/Button'
import Icon from '../Icon/Icon'
import { InputValue } from './InputValue'
import U2FPromptIframeContainer from './U2FPromptIframeContainer'
@@ -18,7 +23,7 @@ const U2FPrompt = ({ application, onValueChange, prompt, buttonRef, contextData
const [authenticatorResponse, setAuthenticatorResponse] = useState<Record<string, unknown> | null>(null)
const [error, setError] = useState('')
if (!application.isFullU2FClient) {
if (!application.isFullU2FClient && !isAndroid()) {
return (
<U2FPromptIframeContainer
contextData={contextData}
@@ -28,48 +33,71 @@ const U2FPrompt = ({ application, onValueChange, prompt, buttonRef, contextData
}}
/>
)
} else {
return (
<div className="min-w-76">
{error && <div className="text-red-500">{error}</div>}
<Button
primary
fullWidth
colorStyle={authenticatorResponse ? 'success' : 'info'}
onClick={async () => {
const usernameOrError = Username.create((contextData as { username: string }).username)
if (usernameOrError.isFailed()) {
setError(usernameOrError.getError())
return
}
const username = usernameOrError.getValue()
let authenticatorResponse: Record<string, unknown> | null = null
if (isAndroid()) {
const authenticatorOptionsOrError = await application.getAuthenticatorAuthenticationOptions.execute({
username: username.value,
})
if (authenticatorOptionsOrError.isFailed()) {
setError(authenticatorOptionsOrError.getError())
return
}
const authenticatorOptions = authenticatorOptionsOrError.getValue()
authenticatorResponse = await application
.mobileDevice()
.authenticateWithU2F(JSON.stringify(authenticatorOptions))
} else {
const authenticatorResponseOrError = await application.getAuthenticatorAuthenticationResponse.execute({
username: username.value,
})
if (authenticatorResponseOrError.isFailed()) {
setError(authenticatorResponseOrError.getError())
return
}
authenticatorResponse = authenticatorResponseOrError.getValue()
}
if (authenticatorResponse === null) {
setError('Failed to obtain device response')
return
}
setAuthenticatorResponse(authenticatorResponse)
onValueChange(authenticatorResponse, prompt)
}}
ref={buttonRef}
>
{authenticatorResponse ? (
<span className="flex items-center justify-center gap-3">
<Icon type="check-circle" />
Obtained Device Response
</span>
) : (
'Authenticate Device'
)}
</Button>
</div>
)
}
return (
<div className="min-w-76">
{error && <div className="text-red-500">{error}</div>}
<Button
primary
fullWidth
colorStyle={authenticatorResponse ? 'success' : 'info'}
onClick={async () => {
if (!contextData || contextData.username === undefined) {
setError('No username provided')
return
}
const authenticatorResponseOrError = await application.getAuthenticatorAuthenticationResponse.execute({
username: contextData.username as string,
})
if (authenticatorResponseOrError.isFailed()) {
setError(authenticatorResponseOrError.getError())
return
}
const authenticatorResponse = authenticatorResponseOrError.getValue()
setAuthenticatorResponse(authenticatorResponse)
onValueChange(authenticatorResponse, prompt)
}}
ref={buttonRef}
>
{authenticatorResponse ? (
<span className="flex items-center justify-center gap-3">
<Icon type="check-circle" />
Obtained Device Response
</span>
) : (
'Authenticate Device'
)}
</Button>
</div>
)
}
export default U2FPrompt