Skip to content
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_GROUPS=

# oidc config

# Enable legacy username-based OIDC subject identifiers for backwards compatibility.
TINYAUTH_OIDC_LEGACYSUBENABLED=true
# Path to the private key file, including file name.
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
# Path to the public key file, including file name.
Expand Down
12 changes: 7 additions & 5 deletions cmd/tinyauth/tinyauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"fmt"
"os"
"reflect"
"strings"

"charm.land/huh/v2"
Expand Down Expand Up @@ -32,10 +31,13 @@ func main() {
Configuration: tConfig,
Resources: loaders,
Run: func(_ []string) error {
// enable this on experimental features
if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) {
colors := getColors()
fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.")
colors := getColors()
res := tConfig.Validate()
if len(res.Errors) > 0 {
return fmt.Errorf("invalid configuration: %s", strings.Join(res.Errors, ", "))
}
for _, warn := range res.Warnings {
fmt.Println(colors.yellow.Render("⚠") + " " + warn)
}
return runCmd(*tConfig)
},
Expand Down
5 changes: 0 additions & 5 deletions internal/bootstrap/app_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,6 @@ func (app *BootstrapApp) Setup() error {
app.runtime.OAuthProviders[id] = provider
}

// cookie domain
if !app.config.Auth.SubdomainsEnabled {
app.log.App.Warn().Msg("Subdomains are disabled, cookies will be set for the current domain only")
}

cookieDomain, err := utils.GetCookieDomain(app.runtime.AppURL, app.config.Auth.SubdomainsEnabled)

if err != nil {
Expand Down
45 changes: 40 additions & 5 deletions internal/model/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package model

import (
"os"
"reflect"
"time"
)

Expand Down Expand Up @@ -80,8 +81,9 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
},
},
OIDC: OIDCConfig{
PrivateKeyPath: "./tinyauth_oidc_key",
PublicKeyPath: "./tinyauth_oidc_key.pub",
LegacySubEnabled: true,
PrivateKeyPath: "./tinyauth_oidc_key",
PublicKeyPath: "./tinyauth_oidc_key.pub",
},
Tailscale: TailscaleConfig{
CacheDuration: int(time.Duration(5 * time.Minute).Seconds()),
Expand Down Expand Up @@ -196,9 +198,10 @@ type OAuthConfig struct {
}

type OIDCConfig struct {
PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath,omitempty"`
PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath,omitempty"`
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients,omitempty"`
LegacySubEnabled bool `description:"Enable legacy username-based OIDC subject identifiers for backwards compatibility." yaml:"legacySubEnabled,omitempty"`
PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath,omitempty"`
PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath,omitempty"`
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients,omitempty"`
}

type UIConfig struct {
Expand Down Expand Up @@ -345,3 +348,35 @@ type AppPath struct {
Allow string `description:"Disable authentication for only paths that match the regex string." yaml:"allow,omitempty"`
Block string `description:"Enable authentication for only paths that match the regex string." yaml:"block,omitempty"`
}

type ValidateResult struct {
Warnings []string `json:"warnings"`
Errors []string `json:"errors"`
}

// Helper config functions

func (c *Config) Validate() ValidateResult {
res := ValidateResult{
Warnings: make([]string, 0),
Errors: make([]string, 0),
}
env := DetectRuntimeEnv()

// warn on experimental features
if !reflect.DeepEqual(NewDefaultConfiguration(env).Experimental, c.Experimental) {
res.Warnings = append(res.Warnings, "Experimental features are enabled, use with caution. Experimental features may change with each release")
}

// warn on subdomains disabled
if !c.Auth.SubdomainsEnabled {
res.Warnings = append(res.Warnings, "Subdomains are disabled, cookies will be set for the current domain only")
}

// warn on legacy sub
if c.OIDC.LegacySubEnabled {
res.Warnings = append(res.Warnings, "Legacy username-based OIDC subject identifiers are enabled, this is insecure and will be removed in the next major release")
}

return res
}
10 changes: 9 additions & 1 deletion internal/service/oidc_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,15 @@ func (service *OIDCService) hashAndEncodePKCE(codeVerifier string) string {
// We will just create a uuid out of the username and client name which remains stable,
// but if username or client name changes then sub changes too.
func (service *OIDCService) CreateSub(userContext model.UserContext, clientId string) string {
return utils.GenerateUUID(fmt.Sprintf("%s:%s", userContext.GetUsername(), clientId))
sub := fmt.Sprintf("%q:%q:%q", userContext.GetProviderID(), userContext.GetUsername(), clientId)

// The old sub created by the username and client ID is insecure
// because it allows subs from different providers to be the same
if service.config.OIDC.LegacySubEnabled {
sub = fmt.Sprintf("%s:%s", userContext.GetUsername(), clientId)
}

return utils.GenerateUUID(sub)
}

func (service *OIDCService) IsCodeUsed(codeHash string) (string, bool) {
Expand Down
Loading