Skip to content

Commit

Permalink
feat: add a new admin API to remove a specific 2nd factor credential (#…
Browse files Browse the repository at this point in the history
…2962)

Closes #2505
  • Loading branch information
supercairos authored Jan 28, 2023
1 parent 132255e commit 44556a4
Show file tree
Hide file tree
Showing 35 changed files with 1,014 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"credentials": {
"webauthn": {
"type": "webauthn",
"identifiers": [],
"config": {
"credentials": [
{
"public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=",
"attestation_type": "none",
"authenticator": {
"aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09",
"sign_count": 0,
"clone_warning": false
},
"display_name": "test",
"added_at": "2022-12-16T14:11:55Z",
"is_passwordless": true
},
{
"public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=",
"attestation_type": "none",
"authenticator": {
"aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09",
"sign_count": 0,
"clone_warning": false
},
"display_name": "test",
"added_at": "2022-12-16T14:11:55Z",
"is_passwordless": true
}
],
"user_handle": "RWY1SmlNcE1Sd3V6YXVXcy85SjBnUT09"
},
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"credentials": {
"webauthn": {
"type": "webauthn",
"identifiers": [],
"config": {
"credentials": [
{
"public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=",
"attestation_type": "none",
"authenticator": {
"aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09",
"sign_count": 0,
"clone_warning": false
},
"display_name": "test",
"added_at": "2022-12-16T14:11:55Z",
"is_passwordless": true
},
{
"public_key": "cFFFQ0F5WWdBU0ZZSU1KTFFoSnhRUnpobktQVGNQQ1VPRE9teFlEWW8yb2JybTliaHA1bHZTWjNJbGdnWGpoWnZKYVBVcUY5UFhxWnFUZFdZUFI3UitiMm4vV2krSXhLS1hzUzRyVT0=",
"attestation_type": "none",
"authenticator": {
"aaguid": "cmM0QUFqVzh4Z3BraXdzbDhmQlZBdz09",
"sign_count": 0,
"clone_warning": false
},
"display_name": "test",
"added_at": "2022-12-16T14:11:55Z",
"is_passwordless": true
}
],
"user_handle": "RWY1SmlNcE1Sd3V6YXVXcy85SjBnUT09"
},
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"credentials": {
"webauthn": {
"type": "webauthn",
"identifiers": [],
"config": {
"credentials": [
{
"public_key": "pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU=",
"attestation_type": "none",
"authenticator": {
"aaguid": "rc4AAjW8xgpkiwsl8fBVAw==",
"sign_count": 0,
"clone_warning": false
},
"display_name": "test",
"added_at": "2022-12-16T14:11:55Z",
"is_passwordless": true
}
],
"user_handle": "Ef5JiMpMRwuzauWs/9J0gQ=="
},
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"credentials": {
"webauthn": {
"type": "webauthn",
"identifiers": [],
"config": {
"credentials": [
{
"public_key": "pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU=",
"attestation_type": "none",
"authenticator": {
"aaguid": "rc4AAjW8xgpkiwsl8fBVAw==",
"sign_count": 0,
"clone_warning": false
},
"display_name": "test",
"added_at": "2022-12-16T14:11:55Z",
"is_passwordless": true
}
],
"user_handle": "Ef5JiMpMRwuzauWs/9J0gQ=="
},
"version": 1
}
},
"schema_id": "default",
"state": "active",
"traits": {},
"metadata_public": null,
"metadata_admin": null
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package lookup
package identity

import (
"time"
Expand All @@ -13,12 +13,12 @@ import (
)

// CredentialsConfig is the struct that is being used as part of the identity credentials.
type CredentialsConfig struct {
type CredentialsLookupConfig struct {
// List of recovery codes
RecoveryCodes []RecoveryCode `json:"recovery_codes"`
}

func (c *CredentialsConfig) ToNode() *node.Node {
func (c *CredentialsLookupConfig) ToNode() *node.Node {
messages := make([]text.Message, len(c.RecoveryCodes))
formatted := make([]string, len(c.RecoveryCodes))
for k, code := range c.RecoveryCodes {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package lookup_test
package identity_test

import (
_ "embed"
"testing"
"time"

"github.com/ory/kratos/identity"
"github.com/ory/kratos/internal/testhelpers"
"github.com/ory/kratos/selfservice/strategy/lookup"

"github.com/ory/x/sqlxx"
)

func TestToNode(t *testing.T) {
c := lookup.CredentialsConfig{RecoveryCodes: []lookup.RecoveryCode{
c := identity.CredentialsLookupConfig{RecoveryCodes: []identity.RecoveryCode{
{Code: "foo", UsedAt: sqlxx.NullTime(time.Unix(1629199958, 0).UTC())},
{Code: "bar"},
{Code: "baz"},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package totp
package identity

// CredentialsConfig is the struct that is being used as part of the identity credentials.
type CredentialsConfig struct {
type CredentialsTOTPConfig struct {
// TOTPURL is the TOTP URL
//
// For more details see: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,49 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package webauthn
package identity

import (
"time"

"github.com/ory/kratos/identity"

"github.com/duo-labs/webauthn/webauthn"
)

// CredentialsConfig is the struct that is being used as part of the identity credentials.
type CredentialsConfig struct {
type CredentialsWebAuthnConfig struct {
// List of webauthn credentials.
Credentials Credentials `json:"credentials"`
UserHandle []byte `json:"user_handle"`
Credentials CredentialsWebAuthn `json:"credentials"`
UserHandle []byte `json:"user_handle"`
}

type Credentials []Credential
type CredentialsWebAuthn []CredentialWebAuthn

func CredentialFromWebAuthn(credential *webauthn.Credential, isPasswordless bool) *Credential {
return &Credential{
func CredentialFromWebAuthn(credential *webauthn.Credential, isPasswordless bool) *CredentialWebAuthn {
return &CredentialWebAuthn{
ID: credential.ID,
PublicKey: credential.PublicKey,
IsPasswordless: isPasswordless,
AttestationType: credential.AttestationType,
Authenticator: Authenticator{
Authenticator: AuthenticatorWebAuthn{
AAGUID: credential.Authenticator.AAGUID,
SignCount: credential.Authenticator.SignCount,
CloneWarning: credential.Authenticator.CloneWarning,
},
}
}

func (c Credentials) ToWebAuthn() (result []webauthn.Credential) {
func (c CredentialsWebAuthn) ToWebAuthn() (result []webauthn.Credential) {
for k := range c {
result = append(result, *c[k].ToWebAuthn())
}
return result
}

func (c Credentials) ToWebAuthnFiltered(aal identity.AuthenticatorAssuranceLevel) (result []webauthn.Credential) {
func (c CredentialsWebAuthn) ToWebAuthnFiltered(aal AuthenticatorAssuranceLevel) (result []webauthn.Credential) {
for k, cc := range c {
if aal == identity.AuthenticatorAssuranceLevel1 && !cc.IsPasswordless {
if aal == AuthenticatorAssuranceLevel1 && !cc.IsPasswordless {
continue
} else if aal == identity.AuthenticatorAssuranceLevel2 && cc.IsPasswordless {
} else if aal == AuthenticatorAssuranceLevel2 && cc.IsPasswordless {
continue
}

Expand All @@ -54,7 +52,7 @@ func (c Credentials) ToWebAuthnFiltered(aal identity.AuthenticatorAssuranceLevel
return result
}

func (c *Credential) ToWebAuthn() *webauthn.Credential {
func (c *CredentialWebAuthn) ToWebAuthn() *webauthn.Credential {
return &webauthn.Credential{
ID: c.ID,
PublicKey: c.PublicKey,
Expand All @@ -67,17 +65,17 @@ func (c *Credential) ToWebAuthn() *webauthn.Credential {
}
}

type Credential struct {
ID []byte `json:"id"`
PublicKey []byte `json:"public_key"`
AttestationType string `json:"attestation_type"`
Authenticator Authenticator `json:"authenticator"`
DisplayName string `json:"display_name"`
AddedAt time.Time `json:"added_at"`
IsPasswordless bool `json:"is_passwordless"`
type CredentialWebAuthn struct {
ID []byte `json:"id"`
PublicKey []byte `json:"public_key"`
AttestationType string `json:"attestation_type"`
Authenticator AuthenticatorWebAuthn `json:"authenticator"`
DisplayName string `json:"display_name"`
AddedAt time.Time `json:"added_at"`
IsPasswordless bool `json:"is_passwordless"`
}

type Authenticator struct {
type AuthenticatorWebAuthn struct {
AAGUID []byte `json:"aaguid"`
SignCount uint32 `json:"sign_count"`
CloneWarning bool `json:"clone_warning"`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package webauthn
package identity

import (
"testing"

"github.com/ory/kratos/identity"

"github.com/duo-labs/webauthn/webauthn"
"github.com/stretchr/testify/assert"
)
Expand All @@ -27,16 +25,16 @@ func TestCredentialConversion(t *testing.T) {
actual := CredentialFromWebAuthn(expected, false).ToWebAuthn()
assert.Equal(t, expected, actual)

actualList := Credentials{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(identity.AuthenticatorAssuranceLevel2)
actualList := CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2)
assert.Equal(t, []webauthn.Credential{*expected}, actualList)

actualList = Credentials{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(identity.AuthenticatorAssuranceLevel1)
actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1)
assert.Equal(t, []webauthn.Credential{*expected}, actualList)

actualList = Credentials{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(identity.AuthenticatorAssuranceLevel2)
actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2)
assert.Len(t, actualList, 0)

actualList = Credentials{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(identity.AuthenticatorAssuranceLevel1)
actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1)
assert.Len(t, actualList, 0)

fromWebAuthn := CredentialFromWebAuthn(expected, true)
Expand Down
Loading

0 comments on commit 44556a4

Please sign in to comment.