Skip to content

Commit b6ac894

Browse files
zepatrikory-bot
authored andcommitted
feat: full user-code configuration
GitOrigin-RevId: 640f495fa2caf90025c60218554f4f968b2108ae
1 parent ad05646 commit b6ac894

File tree

4 files changed

+84
-37
lines changed

4 files changed

+84
-37
lines changed

driver/config/provider.go

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import (
77
"context"
88
"crypto/sha512"
99
"fmt"
10-
"maps"
1110
"math"
1211
"net/http"
1312
"net/url"
14-
"slices"
1513
"strings"
1614
"testing"
1715
"time"
@@ -102,7 +100,9 @@ const (
102100
KeySubjectIdentifierAlgorithmSalt = "oidc.subject_identifiers.pairwise.salt"
103101
KeyPublicAllowDynamicRegistration = "oidc.dynamic_client_registration.enabled"
104102
KeyDeviceAuthTokenPollingInterval = "oauth2.device_authorization.token_polling_interval" // #nosec G101
105-
KeyDeviceAuthUserCodeEntropy = "oauth2.device_authorization.user_code_entropy"
103+
KeyDeviceAuthUserCodeEntropyPreset = "oauth2.device_authorization.user_code.entropy_preset"
104+
KeyDeviceAuthUserCodeLength = "oauth2.device_authorization.user_code.length"
105+
KeyDeviceAuthUserCodeCharacterSet = "oauth2.device_authorization.user_code.character_set"
106106
KeyPKCEEnforced = "oauth2.pkce.enforced"
107107
KeyPKCEEnforcedForPublicClients = "oauth2.pkce.enforced_for_public_clients"
108108
KeyLogLevel = "log.level"
@@ -124,15 +124,6 @@ const (
124124

125125
const DSNMemory = "memory"
126126

127-
var userCodeEtropy = map[string]struct {
128-
Length int
129-
Symbols []rune
130-
}{
131-
"high": {Length: 8, Symbols: randx.AlphaNumNoAmbiguous},
132-
"medium": {Length: 8, Symbols: randx.AlphaUpper},
133-
"low": {Length: 9, Symbols: randx.Numeric},
134-
}
135-
136127
var (
137128
_ hasherx.PBKDF2Configurator = (*DefaultProvider)(nil)
138129
_ hasherx.BCryptConfigurator = (*DefaultProvider)(nil)
@@ -437,31 +428,38 @@ func (p *DefaultProvider) GetDeviceAuthTokenPollingInterval(ctx context.Context)
437428
return p.p.DurationF(KeyDeviceAuthTokenPollingInterval, time.Second*5)
438429
}
439430

431+
func (p *DefaultProvider) userCodeEntropyPreset(t string) (int, []rune) {
432+
switch t {
433+
default:
434+
p.l.Errorf(`invalid user code entropy preset %q, allowed values are "high", "medium", or "low"`, t)
435+
fallthrough
436+
case "high":
437+
return 8, randx.AlphaNumNoAmbiguous
438+
case "medium":
439+
return 8, randx.AlphaUpper
440+
case "low":
441+
return 9, randx.Numeric
442+
}
443+
}
444+
440445
// GetUserCodeLength returns configured user_code length
441446
func (p *DefaultProvider) GetUserCodeLength(ctx context.Context) int {
442-
k := p.getProvider(ctx).StringF(KeyDeviceAuthUserCodeEntropy, "medium")
443-
profile, ok := userCodeEtropy[k]
444-
if !ok {
445-
p.l.WithError(errors.Errorf("Invalid user_code entropy: %s, allowed entropy values are: %s", k, strings.Join(slices.Collect(maps.Keys(userCodeEtropy)), ", ")))
446-
return 0
447+
if l := p.getProvider(ctx).Int(KeyDeviceAuthUserCodeLength); l > 0 {
448+
return l
447449
}
448-
return profile.Length
450+
k := p.getProvider(ctx).StringF(KeyDeviceAuthUserCodeEntropyPreset, "high")
451+
l, _ := p.userCodeEntropyPreset(k)
452+
return l
449453
}
450454

451-
// GetDeviceAuthTokenPollingInterval returns configured user_code allowed symbols
455+
// GetUserCodeSymbols returns configured user_code allowed symbols
452456
func (p *DefaultProvider) GetUserCodeSymbols(ctx context.Context) []rune {
453-
k := p.getProvider(ctx).StringF(KeyDeviceAuthUserCodeEntropy, "medium")
454-
profile, ok := userCodeEtropy[k]
455-
if !ok {
456-
keys := []string{}
457-
for k := range userCodeEtropy {
458-
keys = append(keys, k)
459-
}
460-
461-
p.l.WithError(errors.Errorf("Invalid user_code entropy: %s, allowed entropy values are: %s", k, keys))
462-
return nil
457+
if s := p.getProvider(ctx).String(KeyDeviceAuthUserCodeCharacterSet); s != "" {
458+
return []rune(s)
463459
}
464-
return profile.Symbols
460+
k := p.getProvider(ctx).StringF(KeyDeviceAuthUserCodeEntropyPreset, "high")
461+
_, s := p.userCodeEntropyPreset(k)
462+
return s
465463
}
466464

467465
func (p *DefaultProvider) LoginURL(ctx context.Context) *url.URL {

driver/config/provider_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func TestProviderValidates(t *testing.T) {
326326
assert.Equal(t, true, c.GetEnforcePKCEForPublicClients(ctx))
327327
assert.Equal(t, 2*time.Hour, c.GetDeviceAuthTokenPollingInterval(ctx))
328328
assert.Equal(t, 8, c.GetUserCodeLength(ctx))
329-
assert.Equal(t, randx.AlphaUpper, c.GetUserCodeSymbols(ctx))
329+
assert.Equal(t, string(randx.AlphaUpper), string(c.GetUserCodeSymbols(ctx)))
330330

331331
// secrets
332332
secret, err := c.GetGlobalSecret(ctx)
@@ -531,3 +531,23 @@ func TestJWTScopeClaimStrategy(t *testing.T) {
531531
p.MustSet(ctx, KeyJWTScopeClaimStrategy, "both")
532532
assert.Equal(t, jwt.JWTScopeFieldBoth, p.GetJWTScopeField(ctx))
533533
}
534+
535+
func TestDeviceUserCode(t *testing.T) {
536+
l := logrusx.New("", "")
537+
538+
t.Run("preset", func(t *testing.T) {
539+
p := MustNew(t, l, configx.WithValue(KeyDeviceAuthUserCodeEntropyPreset, "low"))
540+
assert.Equal(t, 9, p.GetUserCodeLength(t.Context()))
541+
assert.Equal(t, string(randx.Numeric), string(p.GetUserCodeSymbols(t.Context())))
542+
})
543+
544+
t.Run("explicit values", func(t *testing.T) {
545+
length, charSet := 15, "foobarbaz1234"
546+
p := MustNew(t, l, configx.WithValues(map[string]any{
547+
KeyDeviceAuthUserCodeLength: length,
548+
KeyDeviceAuthUserCodeCharacterSet: charSet,
549+
}))
550+
assert.Equal(t, length, p.GetUserCodeLength(t.Context()))
551+
assert.Equal(t, charSet, string(p.GetUserCodeSymbols(t.Context())))
552+
})
553+
}

internal/.hydra.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ oauth2:
126126
cost: 20
127127
device_authorization:
128128
token_polling_interval: 2h
129-
user_code_entropy: medium
129+
user_code:
130+
entropy_preset: medium
130131
pkce:
131132
enforced: true
132133
enforced_for_public_clients: true

spec/config.json

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -926,11 +926,39 @@
926926
"description": "Configures how often a non-interactive device should poll the device token endpoint, this is a purely informational configuration and does not enforce rate-limiting.",
927927
"examples": ["5s", "15s", "1m"]
928928
},
929-
"user_code_entropy": {
930-
"type": "string",
931-
"description": "Sets the entropy for the user codes.",
932-
"default": "medium",
933-
"enum": ["high", "medium", "low"]
929+
"user_code": {
930+
"type": "object",
931+
"description": "Configures the user code settings.",
932+
"oneOf": [
933+
{
934+
"properties": {
935+
"entropy_preset": {
936+
"type": "string",
937+
"description": "Presets for the user-code length and character set.",
938+
"enum": ["high", "medium", "low"]
939+
}
940+
},
941+
"required": ["entropy_preset"],
942+
"additionalProperties": false
943+
},
944+
{
945+
"properties": {
946+
"length": {
947+
"type": "integer",
948+
"description": "The length of the user code.",
949+
"minimum": 6
950+
},
951+
"character_set": {
952+
"type": "string",
953+
"description": "The character set to use for the user code. Provide the raw characters that should be used.",
954+
"examples": ["ABCDEFGHJKLMNPQRSTUVWXYZ23456789"],
955+
"minLength": 8
956+
}
957+
},
958+
"required": ["length", "character_set"],
959+
"additionalProperties": false
960+
}
961+
]
934962
}
935963
}
936964
},

0 commit comments

Comments
 (0)