-
Notifications
You must be signed in to change notification settings - Fork 27
Implement Webauthn #8393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
robert-oleynik
wants to merge
65
commits into
master
Choose a base branch
from
webauthn2
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Implement Webauthn #8393
Changes from 49 commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
cd974e6
add WebAuthn credentials to database schema
robert-oleynik 47f52ac
setup WebAuthnCredentials DAO
robert-oleynik c522883
Implement WebAuthn backend
robert-oleynik 8938d9e
add frontend webauthn support lib
f5130f6
use proper file extension in some files
09afa8a
WIP: Add passkey management page
86260d8
WIP: add some frontend part for passkey registration
1fe36d9
setup tls proxy
robert-oleynik 391ec47
fix webauthn registration start
robert-oleynik aba3d99
fix webauthn registration finalize
robert-oleynik 4f03669
fix webauthn auth start
robert-oleynik b9014a0
fix key id and authentication process
robert-oleynik 3447565
fix frontend redirect
robert-oleynik 3cf61e6
restyle login form
robert-oleynik dcff035
move user key id to separate datbase field
robert-oleynik 898abda
fix authentication failures
robert-oleynik f68ab95
display and remove webauthn keys
robert-oleynik 7ba0238
wrap blocking calls
robert-oleynik dc53bd0
increment schema version
robert-oleynik dcf027d
fix schema versioning
robert-oleynik a42a10d
fix schema field typo
robert-oleynik 636de42
add reversion for database evolution
robert-oleynik 5069e9f
fix compiler errors
robert-oleynik ff9fb7b
fix future box handling
robert-oleynik 23a9591
fix frontend lints
robert-oleynik 7592b13
fix api usage
robert-oleynik 96cf3d6
add trailing ;
robert-oleynik ab95204
apply format to backend
robert-oleynik bce5a1d
fix uri
robert-oleynik e28d477
fix typecheck errors
robert-oleynik 6838401
fixed frontend
robert-oleynik ce831fb
read origin from configuration
robert-oleynik dbccd1f
apply format
robert-oleynik e652f78
fix future exception handling
robert-oleynik 3bdd978
rename PassKeys to Passkeys and add missing await
robert-oleynik f7734e4
merge manage passkeys and change password view
robert-oleynik c827ee5
fix frontend
robert-oleynik 01198f5
catch WebAuthn exception on log in
robert-oleynik 8a495fd
minor fixes
robert-oleynik d263bec
fix frontend
robert-oleynik 69885d7
rework webAuthnRegistrationStart
robert-oleynik 9987c58
add json format for WebAuthn types
robert-oleynik 699ac9d
setup webauthn registration
robert-oleynik 9157c18
Add webauthn4j
robert-oleynik e65a551
Merge branch 'master' into webauthn2
robert-oleynik 8e4771e
Merge branch 'master' into webauthn2
robert-oleynik 881df01
Merge branch 'webauthn2' of github.com:scalableminds/webknossos into …
robert-oleynik 7b95eb9
Add WebAuthnPublicKeyCredentialRequestOptions
robert-oleynik a6ac15f
rework webauthn assertion
robert-oleynik 5c53f6d
remove old webauthn dependencies
robert-oleynik 6cd7b7f
Merge branch 'master' into webauthn2
robert-oleynik 1c16b1a
Merge branch 'master' into webauthn2
robert-oleynik 35db834
fix merge artifacts
robert-oleynik 8814f2f
fix registration flow
robert-oleynik bf2fa71
move encoding to backend
robert-oleynik 9034377
fix frontend
robert-oleynik da5c092
apply review comments
robert-oleynik 4663fee
Merge branch 'master' into webauthn2
robert-oleynik 50b830a
fix frontend
robert-oleynik 1f1b0b3
apply some fixes
robert-oleynik 2c32d15
format and lint backend
robert-oleynik fa78c96
use CollectionConverters
robert-oleynik 48b5f83
apply format
robert-oleynik 5dd64dc
fix application conf
robert-oleynik c449e89
add coderabbit suggestions
robert-oleynik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package models.user | ||
|
||
import com.fasterxml.jackson.core.`type`.TypeReference | ||
import com.fasterxml.jackson.annotation.{JsonCreator, JsonProperty, JsonTypeInfo} | ||
import com.scalableminds.util.accesscontext.DBAccessContext | ||
import com.scalableminds.util.objectid.ObjectId | ||
import com.scalableminds.util.tools.Fox | ||
import com.scalableminds.webknossos.schema.Tables._ | ||
import com.webauthn4j.converter.AttestedCredentialDataConverter | ||
import com.webauthn4j.converter.util.ObjectConverter | ||
import com.webauthn4j.credential.CredentialRecordImpl | ||
import com.webauthn4j.data.attestation.statement.AttestationStatement | ||
import com.webauthn4j.data.extension.authenticator.{AuthenticationExtensionsAuthenticatorOutputs, RegistrationExtensionAuthenticatorOutput} | ||
import net.liftweb.common.Box.tryo | ||
import slick.lifted.Rep | ||
import utils.sql.{SQLDAO, SqlClient} | ||
|
||
import javax.inject.Inject | ||
import scala.concurrent.ExecutionContext | ||
|
||
|
||
case class WebAuthnCredential( | ||
_id: ObjectId, | ||
_multiUser: ObjectId, | ||
name: String, | ||
credentialRecord: CredentialRecordImpl, | ||
isDeleted: Boolean, | ||
) { | ||
def serializeAttestationStatement(converter: ObjectConverter): Array[Byte] = { | ||
AttestationStatementEnvelope(credentialRecord.getAttestationStatement).serialize(converter) | ||
} | ||
|
||
def serializeAttestedCredential(objectConverter: ObjectConverter): Array[Byte] = { | ||
val converter = new AttestedCredentialDataConverter(objectConverter); | ||
converter.convert(credentialRecord.getAttestedCredentialData) | ||
} | ||
|
||
def serializedExtensions(converter: ObjectConverter): Array[Byte] = { | ||
converter.getCborConverter.writeValueAsBytes(credentialRecord.getAuthenticatorExtensions) | ||
} | ||
} | ||
|
||
// Define the AttestationStatementEnvelope class | ||
case class AttestationStatementEnvelope(@JsonProperty("attStmt") attestationStatement: AttestationStatement) { | ||
// The JSON type information annotation for polymorphism | ||
@JsonTypeInfo( | ||
use = JsonTypeInfo.Id.NAME, | ||
include = JsonTypeInfo.As.EXTERNAL_PROPERTY, | ||
property = "fmt" | ||
) | ||
private val attStmt: AttestationStatement = attestationStatement | ||
|
||
// Getter for the 'fmt' property | ||
@JsonProperty("fmt") | ||
def getFormat: String = attestationStatement.getFormat | ||
|
||
// Getter for the AttestationStatement instance | ||
def getAttestationStatement: AttestationStatement = attestationStatement | ||
|
||
def serialize(converter: ObjectConverter): Array[Byte] = | ||
converter.getJsonConverter.writeValueAsBytes(this) | ||
} | ||
|
||
class WebAuthnCredentialDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) | ||
extends SQLDAO[WebAuthnCredential, WebauthncredentialsRow, Webauthncredentials](sqlClient) { | ||
protected val collection = Webauthncredentials | ||
|
||
override protected def idColumn(x: Webauthncredentials): Rep[String] = x._Id | ||
|
||
override protected def isDeletedColumn(x: Webauthncredentials): Rep[Boolean] = x.isdeleted | ||
|
||
protected def parse(r: WebauthncredentialsRow): Fox[WebAuthnCredential] = { | ||
val objectConverter = new ObjectConverter() | ||
val converter = objectConverter.getCborConverter | ||
val attestedCredentialDataConverter = new AttestedCredentialDataConverter(objectConverter) | ||
for { | ||
envelope <- tryo(converter.readValue(r.serializedattestationstatement, new TypeReference[AttestationStatementEnvelope] {})) | ||
attestationStatement = envelope.attestationStatement | ||
attestedCredential <- tryo(attestedCredentialDataConverter.convert(r.serializedattestedcredential)) | ||
authenticatorExtensions <- tryo(converter.readValue(r.serializedextensions, new TypeReference[AuthenticationExtensionsAuthenticatorOutputs[RegistrationExtensionAuthenticatorOutput]] {})) | ||
record = new CredentialRecordImpl( | ||
attestationStatement, | ||
null, | ||
null, | ||
null, | ||
r.signaturecount.toLong, | ||
attestedCredential, | ||
authenticatorExtensions, | ||
null, | ||
null, | ||
null | ||
) | ||
} yield WebAuthnCredential(ObjectId(r._Id), ObjectId(r._Multiuser), r.name, record, r.isdeleted) | ||
} | ||
|
||
def findAllForUser(multiUserId: ObjectId)(implicit ctx: DBAccessContext): Fox[List[WebAuthnCredential]] = | ||
for { | ||
accessQuery <- readAccessQuery | ||
r <- run( | ||
q"SELECT $columns FROM webknossos.webauthncredentials WHERE _multiUser = $multiUserId AND $accessQuery" | ||
.as[WebauthncredentialsRow]) | ||
parsed <- parseAll(r) | ||
} yield parsed | ||
|
||
def findByCredentialId(multiUserId: ObjectId, credentialId: Array[Byte])(implicit ctx: DBAccessContext): Fox[WebAuthnCredential] = | ||
for { | ||
accessQuery <- readAccessQuery | ||
r <- run(q"SELECT $columns FROM webknossos.webauthncredentials WHERE _multiUser = $multiUserId AND credentialId = $credentialId AND $accessQuery" | ||
.as[WebauthncredentialsRow]) | ||
parsed <- parseFirst(r, multiUserId) | ||
} yield parsed | ||
|
||
def insertOne(c: WebAuthnCredential): Fox[Unit] = { | ||
val converter = new ObjectConverter() | ||
val serializedAttestationStatement = c.serializeAttestationStatement(converter) | ||
val serializedAttestedCredential = c.serializeAttestedCredential(converter) | ||
val serializedAuthenticatorExtensions = c.serializedExtensions(converter) | ||
val credentialId = c.credentialRecord.getAttestedCredentialData.getCredentialId | ||
for { | ||
_ <- run( | ||
q"""INSERT INTO webknossos.webauthncredentials(_id, _multiUser, credentialId, name, serializedAttestationStatement, serializedAttestedCredential, serializedExtensions, signatureCount) | ||
VALUES(${c._id}, ${c._multiUser}, ${credentialId}, ${c.name}, ${serializedAttestationStatement}, ${serializedAttestedCredential}, | ||
${serializedAuthenticatorExtensions}, ${c.credentialRecord.getCounter.toInt})""".asUpdate) | ||
} yield () | ||
} | ||
|
||
def updateSignCount(c: WebAuthnCredential): Fox[Unit] = { | ||
val signatureCount = c.credentialRecord.getCounter | ||
for { | ||
_ <- run(q"""UPDATE webknossos.webauthncredentials SET signatureCount = $signatureCount WHERE _id = ${c._id}""".asUpdate) | ||
} yield () | ||
} | ||
|
||
def removeById(id: ObjectId, multiUser: ObjectId): Fox[Unit] = | ||
for { | ||
_ <- run(q"""DELETE FROM webknossos.webauthncredentials WHERE _id = ${id} AND _multiUser=${multiUser}""".asUpdate) | ||
} yield () | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
START TRANSACTION; | ||
|
||
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 130, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; | ||
|
||
CREATE TABLE webknossos.webauthnCredentials( | ||
_id TEXT PRIMARY KEY, | ||
_multiUser CHAR(24) NOT NULL, | ||
credentialId BYTEA NOT NULL, | ||
name TEXT NOT NULL, | ||
serializedAttestationStatement BYTEA NOT NULL, | ||
serializedAttestedCredential BYTEA NOT NULL, | ||
serializedExtensions BYTEA NOT NULL, | ||
signatureCount INTEGER NOT NULL, | ||
isDeleted BOOLEAN NOT NULL DEFAULT false, | ||
UNIQUE (_id, credentialId) | ||
); | ||
|
||
ALTER TABLE webknossos.webauthnCredentials | ||
ADD FOREIGN KEY (_multiUser) REFERENCES webknossos.multiUsers(_id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; | ||
|
||
UPDATE webknossos.releaseInformation SET schemaVersion = 131; | ||
|
||
COMMIT TRANSACTION; |
10 changes: 10 additions & 0 deletions
10
conf/evolutions/reversions/131-add-webauthn-credentials.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
START TRANSACTION; | ||
|
||
-- This reversion might take a while because it needs to search in all annotation layer names for '$' and replace it with '' | ||
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 131, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; | ||
|
||
DROP TABLE webknossos.webauthnCredentials; | ||
|
||
UPDATE webknossos.releaseInformation SET schemaVersion = 130; | ||
|
||
COMMIT TRANSACTION; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh thanks for the renaming / fixing the file type ending 🙏 |
File renamed without changes.
File renamed without changes.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.