Skip to content
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

feat: support multiple origins in webauthn config #3380

Merged
merged 1 commit into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
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
16 changes: 10 additions & 6 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"testing"
"time"

"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/gofrs/uuid"
"github.com/inhies/go-bytesize"
kjson "github.com/knadh/koanf/parsers/json"
Expand Down Expand Up @@ -178,7 +178,7 @@ const (
ViperKeyWebAuthnRPDisplayName = "selfservice.methods.webauthn.config.rp.display_name"
ViperKeyWebAuthnRPID = "selfservice.methods.webauthn.config.rp.id"
ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin"
ViperKeyWebAuthnRPIcon = "selfservice.methods.webauthn.config.rp.issuer"
ViperKeyWebAuthnRPOrigins = "selfservice.methods.webauthn.config.rp.origins"
ViperKeyWebAuthnPasswordless = "selfservice.methods.webauthn.config.passwordless"
ViperKeyOAuth2ProviderURL = "oauth2_provider.url"
ViperKeyOAuth2ProviderHeader = "oauth2_provider.headers"
Expand Down Expand Up @@ -1364,14 +1364,18 @@ func (p *Config) WebAuthnForPasswordless(ctx context.Context) bool {
}

func (p *Config) WebAuthnConfig(ctx context.Context) *webauthn.Config {
scheme := p.SelfPublicURL(ctx).Scheme
id := p.GetProvider(ctx).String(ViperKeyWebAuthnRPID)
origin := p.GetProvider(ctx).String(ViperKeyWebAuthnRPOrigin)
origins := p.GetProvider(ctx).StringsF(ViperKeyWebAuthnRPOrigins, []string{stringsx.Coalesce(origin, scheme+"://"+id)})
return &webauthn.Config{
RPDisplayName: p.GetProvider(ctx).String(ViperKeyWebAuthnRPDisplayName),
RPID: p.GetProvider(ctx).String(ViperKeyWebAuthnRPID),
RPOrigin: p.GetProvider(ctx).String(ViperKeyWebAuthnRPOrigin),
RPIcon: p.GetProvider(ctx).String(ViperKeyWebAuthnRPIcon),
RPID: id,
hperl marked this conversation as resolved.
Show resolved Hide resolved
RPOrigins: origins,
AuthenticatorSelection: protocol.AuthenticatorSelection{
UserVerification: protocol.VerificationDiscouraged,
},
EncodeUserIDAsString: false,
}
}

Expand Down
45 changes: 45 additions & 0 deletions driver/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,51 @@ func TestOAuth2Provider(t *testing.T) {
})
}

func TestWebauthn(t *testing.T) {
ctx := context.Background()

t.Run("case=multiple origins", func(t *testing.T) {
conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr,
configx.WithConfigFiles("stub/.kratos.webauthn.origins.yaml"))
require.NoError(t, err)
webAuthnConfig := conf.WebAuthnConfig(ctx)
assert.Equal(t, "https://example.com/webauthn", webAuthnConfig.RPID)
hperl marked this conversation as resolved.
Show resolved Hide resolved
assert.EqualValues(t, []string{
"https://origin-a.example.com",
"https://origin-b.example.com",
"https://origin-c.example.com",
}, webAuthnConfig.RPOrigins)
})

t.Run("case=one origin", func(t *testing.T) {
conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr,
configx.WithConfigFiles("stub/.kratos.webauthn.origin.yaml"))
require.NoError(t, err)
webAuthnConfig := conf.WebAuthnConfig(ctx)
assert.Equal(t, "https://example.com/webauthn", webAuthnConfig.RPID)
assert.EqualValues(t, []string{
"https://origin-a.example.com",
}, webAuthnConfig.RPOrigins)
})

t.Run("case=id as origin", func(t *testing.T) {
conf, err := config.New(ctx, logrusx.New("", ""), os.Stderr,
configx.WithConfigFiles("stub/.kratos.yaml"))
require.NoError(t, err)
webAuthnConfig := conf.WebAuthnConfig(ctx)
assert.Equal(t, "example.com", webAuthnConfig.RPID)
assert.EqualValues(t, []string{
"http://example.com",
}, webAuthnConfig.RPOrigins)
})

t.Run("case=invalid", func(t *testing.T) {
_, err := config.New(ctx, logrusx.New("", ""), os.Stderr,
configx.WithConfigFiles("stub/.kratos.webauthn.invalid.yaml"))
assert.Error(t, err)
})
}

func TestCourierTemplatesConfig(t *testing.T) {
ctx := context.Background()

Expand Down
231 changes: 231 additions & 0 deletions driver/config/stub/.kratos.webauthn.invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
# serve controls the configuration for the http(s) daemon
serve:
admin:
base_url: http://admin.kratos.ory.sh
port: 1234
host: admin.kratos.ory.sh
public:
base_url: http://public.kratos.ory.sh
port: 1235
host: public.kratos.ory.sh

dsn: sqlite://foo.db?mode=memory&_fk=true

log:
level: debug

courier:
smtp:
connection_uri: smtp://foo:bar@baz/

identity:
default_schema_id: default
schemas:
- id: default
url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktdGVzdC1zY2hlbWEiLAogICIkc2NoZW1hIjogImh0dHA6Ly9qc29uLXNjaGVtYS5vcmcvZHJhZnQtMDcvc2NoZW1hIyIsCiAgInRpdGxlIjogIklkZW50aXR5U2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJmaXJzdCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJsYXN0IjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJuYW1lIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9
- id: other
url: base64://ewogICIkaWQiOiAib3J5Oi8vaWRlbnRpdHktb3RoZXItc2NoZW1hIiwKICAiJHNjaGVtYSI6ICJodHRwOi8vanNvbi1zY2hlbWEub3JnL2RyYWZ0LTA3L3NjaGVtYSMiLAogICJ0aXRsZSI6ICJJZGVudGl0eU90aGVyU2NoZW1hIiwKICAidHlwZSI6ICJvYmplY3QiLAogICJwcm9wZXJ0aWVzIjogewogICAgInRyYWl0cyI6IHsKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm90aGVyIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgIH0sCiAgICAgICAgImVtYWlsIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJ0aXRsZSI6ICJlbWFpbCIsCiAgICAgICAgICAib3J5LnNoL2tyYXRvcyI6IHsKICAgICAgICAgICAgImNyZWRlbnRpYWxzIjogewogICAgICAgICAgICAgICJwYXNzd29yZCI6IHsKICAgICAgICAgICAgICAgICJpZGVudGlmaWVyIjogdHJ1ZQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSwKICAgICAgInJlcXVpcmVkIjogWwogICAgICAgICJvdGhlciIsCiAgICAgICAgImVtYWlsIgogICAgICBdLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB0cnVlCiAgICB9CiAgfQp9

hashers:
argon2:
memory: 1MB
iterations: 2
parallelism: 4
salt_length: 16
key_length: 32
dedicated_memory: 1GB
expected_duration: 500ms
expected_deviation: 500ms

secrets:
cookie:
- session-key-7f8a9b77-1
- session-key-7f8a9b77-2
cipher:
- secret-thirty-two-character-long

ciphers:
algorithm: xchacha20-poly1305

selfservice:
default_browser_return_url: http://return-to-3-test.ory.sh/
allowed_return_urls:
- http://return-to-1-test.ory.sh/
- http://return-to-2-test.ory.sh/
- http://*.wildcards.ory.sh
- http://*.sh
- http://*.com
- http://*.com.pl
- http://*
- /return-to-relative-test/
methods:
totp:
enabled: true
config:
issuer: issuer.ory.sh
password:
enabled: true
oidc:
enabled: true
config:
providers:
- id: github
provider: github
client_id: a
client_secret: b
mapper_url: http://test.kratos.ory.sh/default-identity.schema.json
webauthn:
enabled: true
config:
rp:
id: https://example.com/webauthn
hperl marked this conversation as resolved.
Show resolved Hide resolved
display_name: Webauthn
origin: https://origin-a.example.com
origins:
- https://origin-a.example.com
- https://origin-b.example.com
- https://origin-c.example.com
flows:
error:
ui_url: http://test.kratos.ory.sh/error

logout:
after:
default_browser_return_url: http://test.kratos.ory.sh:4000/

recovery:
enabled: true
ui_url: http://test.kratos.ory.sh/recovery
lifespan: 98m
after:
default_browser_return_url: http://test.kratos.ory.sh/dashboard
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_recovery_hook
method: GET
body: /path/to/template.jsonnet

verification:
enabled: true
lifespan: 97m
ui_url: http://test.kratos.ory.sh/verification
after:
default_browser_return_url: http://test.kratos.ory.sh/dashboard
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_verification_hook
method: GET
body: /path/to/template.jsonnet

settings:
ui_url: http://test.kratos.ory.sh/settings
lifespan: 99m
privileged_session_max_age: 5m
after:
default_browser_return_url: https://self-service/settings/return_to
password:
default_browser_return_url: https://self-service/settings/password/return_to
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_settings_password_hook
method: POST
body: /path/to/template.jsonnet
profile:
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_settings_profile_hook
method: POST
body: /path/to/template.jsonnet
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_settings_global_hook
method: POST
body: /path/to/template.jsonnet

login:
ui_url: http://test.kratos.ory.sh/login
lifespan: 99m
before:
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/before_login_hook
method: POST
after:
default_browser_return_url: https://self-service/login/return_to
password:
default_browser_return_url: https://self-service/login/password/return_to
hooks:
- hook: revoke_active_sessions
- hook: require_verified_address
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_login_password_hook
method: POST
body: /path/to/template.jsonnet
auth:
type: basic_auth
config:
user: test-user
password: super-secret
oidc:
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_login_oidc_hook
method: GET
body: /path/to/template.jsonnet
- hook: revoke_active_sessions
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_login_global_hook
method: POST
body: /path/to/template.jsonnet

registration:
enabled: true
ui_url: http://test.kratos.ory.sh/register
lifespan: 98m
before:
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/before_registration_hook
method: GET
after:
default_browser_return_url: https://self-service/registration/return_to
password:
hooks:
- hook: session
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_registration_password_hook
method: POST
body: /path/to/template.jsonnet
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_registration_global_hook
method: POST
body: /path/to/template.jsonnet
auth:
type: api_key
config:
name: My-Key
value: My-Key-Value
in: header
oidc:
default_browser_return_url: https://self-service/registration/oidc/return_to
hooks:
- hook: web_hook
config:
url: https://test.kratos.ory.sh/after_registration_oidc_hook
method: GET
body: /path/to/template.jsonnet
- hook: session
Loading
Loading