Skip to content

Commit

Permalink
examplebroker: Simplify auth modes handling by using a maps of structs
Browse files Browse the repository at this point in the history
We were using a maps of maps that made code hard to read and maintain
and doing some unneeded conversions to json back and forth.

Avoid this by just using structured data instead
  • Loading branch information
3v1n0 committed Nov 8, 2024
1 parent 09d6318 commit be360ff
Showing 1 changed file with 51 additions and 65 deletions.
116 changes: 51 additions & 65 deletions examplebroker/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"errors"
"fmt"
"html/template"
"maps"
"math"
"os"
"sort"
Expand Down Expand Up @@ -55,20 +56,21 @@ const (
webViewID = "webview"
)

const (
ui = "ui"
selectionLabel = "selection_label"
phone = "phone"
wantedCode = "wantedCode"
)
type authMode struct {
selectionLabel string
ui map[string]string
email string
phone string
wantedCode string
}

type sessionInfo struct {
username string
lang string
sessionMode string

currentAuthMode string
allModes map[string]map[string]string
allModes map[string]authMode
attemptsPerMode map[string]int

pwdChange passwdReset
Expand Down Expand Up @@ -279,7 +281,7 @@ func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, s
authMode := allModes[id]
authenticationModes = append(authenticationModes, map[string]string{
layouts.ID: id,
layouts.Label: authMode[selectionLabel],
layouts.Label: authMode.selectionLabel,
})
}
log.Debugf(ctx, "Supported authentication modes for %s: %#v", sessionID, allModes)
Expand All @@ -292,101 +294,101 @@ func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, s
return authenticationModes, nil
}

func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]string) map[string]map[string]string {
allModes := make(map[string]map[string]string)
func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]string) map[string]authMode {
allModes := make(map[string]authMode)
for _, layout := range supportedUILayouts {
switch layout[layouts.Type] {
case layouts.Form:
if layout[layouts.Entry] != "" {
_, supportedEntries := layouts.ParseItems(layout["entry"])
if slices.Contains(supportedEntries, entries.CharsPassword) {
allModes[passwordID] = map[string]string{
allModes[passwordID] = authMode{
selectionLabel: "Password authentication",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Gimme your password",
layouts.Entry: entries.CharsPassword,
}),
},
}
}
if slices.Contains(supportedEntries, entries.Digits) {
allModes[pinCodeID] = map[string]string{
allModes[pinCodeID] = authMode{
selectionLabel: "Pin code",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Enter your pin code",
layouts.Entry: entries.Digits,
}),
},
}
}
if slices.Contains(supportedEntries, entries.Chars) && layout[layouts.Wait] != "" {
allModes[fmt.Sprintf("entry_or_wait_for_%s_gmail.com", sessionInfo.username)] = map[string]string{
allModes[fmt.Sprintf("entry_or_wait_for_%s_gmail.com", sessionInfo.username)] = authMode{
selectionLabel: fmt.Sprintf("Send URL to %[email protected]", sessionInfo.username),
"email": fmt.Sprintf("%[email protected]", sessionInfo.username),
ui: mapToJSON(map[string]string{
email: fmt.Sprintf("%[email protected]", sessionInfo.username),
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: fmt.Sprintf("Click on the link received at %[email protected] or enter the code:", sessionInfo.username),
layouts.Entry: entries.Chars,
layouts.Wait: layouts.True,
}),
},
}
}
}

// The broker could parse the values, that are either true/false
if layout[layouts.Wait] != "" {
if layout[layouts.Button] == layouts.Optional {
allModes[totpWithButtonID] = map[string]string{
allModes[totpWithButtonID] = authMode{
selectionLabel: "Authentication code",
phone: "+33…",
wantedCode: "temporary pass",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Enter your one time credential",
layouts.Entry: entries.Chars,
layouts.Button: "Resend sms",
}),
},
}
} else {
allModes[totpID] = map[string]string{
allModes[totpID] = authMode{
selectionLabel: "Authentication code",
phone: "+33…",
wantedCode: "temporary pass",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Enter your one time credential",
layouts.Entry: entries.Chars,
}),
},
}
}

allModes[phoneAck1ID] = map[string]string{
allModes[phoneAck1ID] = authMode{
selectionLabel: "Use your phone +33…",
phone: "+33…",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Unlock your phone +33… or accept request on web interface:",
layouts.Wait: layouts.True,
}),
},
}

allModes[phoneAck2ID] = map[string]string{
allModes[phoneAck2ID] = authMode{
selectionLabel: "Use your phone +1…",
phone: "+1…",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Unlock your phone +1… or accept request on web interface",
layouts.Wait: layouts.True,
}),
},
}

allModes[fidoDevice1ID] = map[string]string{
allModes[fidoDevice1ID] = authMode{
selectionLabel: "Use your fido device foo",
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.Form,
layouts.Label: "Plug your fido device and press with your thumb",
layouts.Wait: layouts.True,
}),
},
}
}

Expand All @@ -403,14 +405,14 @@ func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]
modeSelectionLabel = "Use a Login code"
modeLabel = "Enter the code in the login page"
}
allModes[modeName] = map[string]string{
allModes[modeName] = authMode{
selectionLabel: modeSelectionLabel,
ui: mapToJSON(map[string]string{
ui: map[string]string{
layouts.Type: layouts.QrCode,
layouts.Label: modeLabel,
layouts.Wait: layouts.True,
layouts.Button: "Regenerate code",
}),
},
}

case webViewID:
Expand All @@ -421,8 +423,8 @@ func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]
return allModes
}

func getMfaModes(info sessionInfo, supportedModes map[string]map[string]string) map[string]map[string]string {
mfaModes := make(map[string]map[string]string)
func getMfaModes(info sessionInfo, supportedModes map[string]authMode) map[string]authMode {
mfaModes := make(map[string]authMode)
for _, mode := range []string{phoneAck1ID, totpWithButtonID, fidoDevice1ID} {
if _, exists := supportedModes[mode]; exists && info.currentAuthMode != mode {
mfaModes[mode] = supportedModes[mode]
Expand All @@ -431,8 +433,8 @@ func getMfaModes(info sessionInfo, supportedModes map[string]map[string]string)
return mfaModes
}

func getPasswdResetModes(info sessionInfo, supportedUILayouts []map[string]string) map[string]map[string]string {
passwdResetModes := make(map[string]map[string]string)
func getPasswdResetModes(info sessionInfo, supportedUILayouts []map[string]string) map[string]authMode {
passwdResetModes := make(map[string]authMode)
for _, layout := range supportedUILayouts {
if layout[layouts.Type] != layouts.NewPassword {
continue
Expand All @@ -454,9 +456,9 @@ func getPasswdResetModes(info sessionInfo, supportedUILayouts []map[string]strin
uiMap[layouts.Button] = "Skip"
}

passwdResetModes[mode] = map[string]string{
passwdResetModes[mode] = authMode{
selectionLabel: "Password reset",
ui: mapToJSON(uiMap),
ui: uiMap,
}
}
return passwdResetModes
Expand Down Expand Up @@ -494,17 +496,17 @@ func (b *Broker) SelectAuthenticationMode(ctx context.Context, sessionID, authen
}

// populate UI options based on selected authentication mode
uiLayoutInfo = jsonToMap(authenticationMode[ui])
uiLayoutInfo = maps.Clone(authenticationMode.ui)

// The broker does extra "out of bound" connections when needed
switch authenticationModeName {
case totpWithButtonID, totpID:
// send sms to sessionInfo.allModes[authenticationModeName][phone]
// send sms to sessionInfo.allModes[authenticationModeName].phone
// add a 0 to simulate new code generation.
authenticationMode[wantedCode] = authenticationMode[wantedCode] + "0"
authenticationMode.wantedCode += "0"
sessionInfo.allModes[authenticationModeName] = authenticationMode
case phoneAck1ID, phoneAck2ID:
// send request to sessionInfo.allModes[authenticationModeName][phone]
// send request to sessionInfo.allModes[authenticationModeName].phone
case fidoDevice1ID:
// start transaction with fido device
case qrCodeAndCodeID, codeID:
Expand Down Expand Up @@ -624,7 +626,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, sessionInfo sessionI
}

case totpWithButtonID, totpID:
wantedCode := sessionInfo.allModes[sessionInfo.currentAuthMode][wantedCode]
wantedCode := sessionInfo.allModes[sessionInfo.currentAuthMode].wantedCode
if challenge != wantedCode {
return auth.Retry, `{"message": "invalid totp code"}`
}
Expand Down Expand Up @@ -787,22 +789,6 @@ func (b *Broker) UserPreCheck(ctx context.Context, username string) (string, err
return userInfoFromName(username), nil
}

func mapToJSON(input map[string]string) string {
data, err := json.Marshal(input)
if err != nil {
panic(fmt.Sprintf("Invalid map data: %v", err))
}
return string(data)
}

func jsonToMap(data string) map[string]string {
r := make(map[string]string)
if err := json.Unmarshal([]byte(data), &r); err != nil {
panic(fmt.Sprintf("Invalid map data: %v", err))
}
return r
}

// decryptAES is just here to illustrate the encryption and decryption
// and in no way the right way to perform a secure encryption
//
Expand Down

0 comments on commit be360ff

Please sign in to comment.