Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,51 @@
import { startAuthentication } from '@simplewebauthn/browser'
import { log, LoggingDomain } from '@/Logging'

const CHROME_CLIPPER_EXTENSION_ORIGIN = 'chrome-extension://heapafmadojoodklnkhjanbinemaagok'
const FIREFOX_CLIPPER_EXTENSION_ORIGIN = 'moz-extension://2a461925-d1b1-4ed3-99a6-91fe7633cc2c'

const ALLOWED_PARENT_ORIGINS = ['file://', CHROME_CLIPPER_EXTENSION_ORIGIN, FIREFOX_CLIPPER_EXTENSION_ORIGIN]

const isAllowedParentOrigin = (origin: string): boolean => {
return ALLOWED_PARENT_ORIGINS.includes(origin)
}

/**
* An iframe for use in the desktop and mobile application that allows them to load app.standardnotes.com to perform
* U2F authentication. Web applications do not need this iframe, as they can perform U2F authentication directly.
* An iframe for use in the desktop app and web clipper extension that allows them to load app.standardnotes.com
* to perform U2F authentication. Web applications do not need this iframe, as they can perform U2F authentication
* directly.
*/
const U2FAuthIframe = () => {
const [username, setUsername] = useState('')
const [apiHost, setApiHost] = useState<string | null>(null)
const [source, setSource] = useState<MessageEvent['source'] | null>(null)
const NATIVE_CLIENT_ORIGIN = 'file://'
const [parentOrigin, setParentOrigin] = useState<string | null>(null)

useEffect(() => {
window.parent.postMessage(
{
mountedAuthView: true,
},
NATIVE_CLIENT_ORIGIN,
)
for (const origin of ALLOWED_PARENT_ORIGINS) {
window.parent.postMessage(
{
mountedAuthView: true,
},
origin,
)
}
}, [])

useEffect(() => {
const messageHandler = (event: MessageEvent) => {
log(LoggingDomain.U2F, 'U2F iframe received message', event)

const eventDoesNotComeFromNativeClient = event.origin !== NATIVE_CLIENT_ORIGIN
if (eventDoesNotComeFromNativeClient) {
log(LoggingDomain.U2F, 'Not setting username; origin does not match', event.origin, NATIVE_CLIENT_ORIGIN)
if (!isAllowedParentOrigin(event.origin)) {
log(LoggingDomain.U2F, 'Not setting username; origin is not allowed', event.origin)
return
}

if (event.data.username) {
setUsername(event.data.username)
setApiHost(event.data.apiHost)
setSource(event.source)
setParentOrigin(event.origin)
}
}

Expand All @@ -54,7 +66,7 @@
setError('')

try {
if (!username || !source) {
if (!username || !source || !parentOrigin) {

Check failure

Code scanning / CodeQL

User-controlled bypass of security check High

This condition guards a sensitive
action
, but a
user-provided value
controls it.

Check failure

Code scanning / CodeQL

User-controlled bypass of security check High

This condition guards a sensitive
action
, but a
user-provided value
controls it.
throw new Error('No username provided')
}

Expand All @@ -81,7 +93,7 @@
{
assertionResponse,
},
NATIVE_CLIENT_ORIGIN,
parentOrigin,
)

setInfo('Authentication successful!')
Expand All @@ -92,7 +104,7 @@
setError(JSON.stringify(error))
console.error(error)
}
}, [source, username, apiHost])
}, [source, username, apiHost, parentOrigin])

return (
<div className="flex h-full w-full flex-col items-center justify-center gap-2">
Expand Down
Loading