diff --git a/hscontrol/auth_test.go b/hscontrol/auth_test.go index a2de2a6a3..9549fd1f1 100644 --- a/hscontrol/auth_test.go +++ b/hscontrol/auth_test.go @@ -2710,15 +2710,11 @@ func runInteractiveWorkflowTest(t *testing.T, tt struct { // extractRegistrationIDFromAuthURL extracts the registration ID from an AuthURL. func extractRegistrationIDFromAuthURL(authURL string) (types.AuthID, error) { // AuthURL format: "http://localhost/register/abc123" - const registerPrefix = "/register/" - - idx := strings.LastIndex(authURL, registerPrefix) - if idx == -1 { + _, idStr, found := strings.CutLast(authURL, "/register/") + if !found { return "", fmt.Errorf("invalid AuthURL format: %s", authURL) //nolint:err113 } - idStr := authURL[idx+len(registerPrefix):] - return types.AuthIDFromString(idStr) } diff --git a/hscontrol/oidc.go b/hscontrol/oidc.go index bd660e4d1..fb2e6839c 100644 --- a/hscontrol/oidc.go +++ b/hscontrol/oidc.go @@ -515,8 +515,8 @@ func validateOIDCAllowedDomains( claims *types.OIDCClaims, ) error { if len(allowedDomains) > 0 { - if at := strings.LastIndex(claims.Email, "@"); at < 0 || - !slices.Contains(allowedDomains, claims.Email[at+1:]) { + if _, domain, found := strings.CutLast(claims.Email, "@"); !found || + !slices.Contains(allowedDomains, domain) { return NewHTTPError(http.StatusUnauthorized, "unauthorised domain", errOIDCAllowedDomains) } } diff --git a/hscontrol/policy/v2/filter.go b/hscontrol/policy/v2/filter.go index 6eb99707f..cd4138b35 100644 --- a/hscontrol/policy/v2/filter.go +++ b/hscontrol/policy/v2/filter.go @@ -511,16 +511,16 @@ func resolveLocalparts( continue } - atIdx := strings.LastIndex(user.Email, "@") - if atIdx < 0 { + localpart, emailDomain, found := strings.CutLast(user.Email, "@") + if !found { continue } - if !strings.EqualFold(user.Email[atIdx+1:], domain) { + if !strings.EqualFold(emailDomain, domain) { continue } - result[user.ID] = user.Email[:atIdx] + result[user.ID] = localpart } } diff --git a/hscontrol/policy/v2/types.go b/hscontrol/policy/v2/types.go index 551f77425..3f748823e 100644 --- a/hscontrol/policy/v2/types.go +++ b/hscontrol/policy/v2/types.go @@ -3076,14 +3076,11 @@ func (u SSHUser) ParseLocalpart() (string, error) { pattern := strings.TrimPrefix(string(u), SSHUserLocalpartPrefix) // Must be *@ - atIdx := strings.LastIndex(pattern, "@") - if atIdx < 0 { + localPart, domain, found := strings.CutLast(pattern, "@") + if !found { return "", fmt.Errorf("%w: missing @ in %q", ErrInvalidLocalpart, u) } - localPart := pattern[:atIdx] - domain := pattern[atIdx+1:] - if localPart != "*" { return "", fmt.Errorf("%w: local part must be *, got %q in %q", ErrInvalidLocalpart, localPart, u) } diff --git a/hscontrol/policy/v2/utils.go b/hscontrol/policy/v2/utils.go index 370ec43eb..01f739355 100644 --- a/hscontrol/policy/v2/utils.go +++ b/hscontrol/policy/v2/utils.go @@ -31,7 +31,7 @@ var ( // // Brackets are only accepted around IPv6 addresses, not IPv4, hostnames, or other alias types. // Bracket stripping reduces both forms to bare "addr:port" or "addr/prefix:port", -// which the normal [strings.LastIndex] of ":" split handles correctly because +// which the normal [strings.CutLast] of ":" split handles correctly because // port strings never contain colons. func splitDestinationAndPort(input string) (string, string, error) { // Handle RFC 3986 bracketed IPv6 (e.g. "[::1]:80" or "[fd7a::1]/128:80,443"). @@ -59,26 +59,21 @@ func splitDestinationAndPort(input string) (string, string, error) { input = host + rest } - // Find the last occurrence of the colon character - lastColonIndex := strings.LastIndex(input, ":") - - // Check if the colon character is present and not at the beginning or end of the string - if lastColonIndex == -1 { + // CutLast returns (input, "", false) when no colon is present, so the + // !found check must come before the emptiness checks below. + destination, port, found := strings.CutLast(input, ":") + if !found { return "", "", ErrInputMissingColon } - if lastColonIndex == 0 { + if destination == "" { return "", "", ErrInputStartsWithColon } - if lastColonIndex == len(input)-1 { + if port == "" { return "", "", ErrInputEndsWithColon } - // Split the string into destination and port based on the last colon - destination := input[:lastColonIndex] - port := input[lastColonIndex+1:] - return destination, port, nil }