Skip to content
Open
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
12 changes: 11 additions & 1 deletion internal/tokens/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,8 +669,18 @@ func (s *Service) GenerateAccessToken(r *http.Request, tx *storage.Connection, p
return "", 0, terr
}

var expiresAt time.Time

issuedAt := s.now().UTC()
expiresAt := issuedAt.Add(time.Second * time.Duration(config.JWT.Exp))
if config.Sessions.AllowLowAAL != nil && *config.Sessions.AllowLowAAL != 0 && models.CompareAAL(aal, params.User.HighestPossibleAAL()) < 0 {
// if user has mfa enabled and the session has not yet been upgraded
// and Limit duration of AAL1 sessions is enabled
// expiresAt should be set to the maximum duration for low aal sessions
expiresAt = session.CreatedAt.UTC().Add(*config.Sessions.AllowLowAAL)
} else {
expiresAt = issuedAt.Add(time.Second * time.Duration(config.JWT.Exp))
}

var clientID string
if params.ClientID != nil && *params.ClientID != uuid.Nil {
clientID = params.ClientID.String()
Expand Down
111 changes: 111 additions & 0 deletions internal/tokens/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,3 +1192,114 @@ func TestAsRedirectURL(t *testing.T) {
require.Contains(t, fragment, "sb", "Fragment should contain Supabase Auth identifier 'sb'")
require.Equal(t, "", fragment.Get("sb"), "Supabase Auth identifier should have empty value")
}

func TestGenerateAccessTokenAllowLowAAL(t *testing.T) {
config, err := conf.LoadGlobal("../../hack/test.env")
require.NoError(t, err)

conn, err := test.SetupDBConnection(config)
require.NoError(t, err)
defer conn.Close()

allowLowAAL := 5 * time.Minute

req, err := http.NewRequest("POST", "https://example.com/", nil)
require.NoError(t, err)

now := time.Now().UTC().Truncate(time.Second)

t.Run("AAL1 session for MFA user uses AllowLowAAL expiry", func(t *testing.T) {
models.TruncateAll(conn)

u, err := models.NewUser("", "test@example.com", "password", "authenticated", nil)
require.NoError(t, err)
require.NoError(t, conn.Create(u))

// Add a verified TOTP factor so HighestPossibleAAL() returns AAL2
factor := models.NewFactor(u, "my-totp", models.TOTP, models.FactorStateVerified)
require.NoError(t, conn.Create(factor))
require.NoError(t, conn.Eager().Find(u, u.ID))

session, err := models.NewSession(u.ID, nil)
require.NoError(t, err)
// Session stays at AAL1 (default)
require.NoError(t, conn.Create(session))

cfg := *config
cfg.Sessions.AllowLowAAL = &allowLowAAL

srv := NewService(&cfg, &panicHookManager{})
srv.SetTimeFunc(func() time.Time { return now })

_, expiresAt, err := srv.GenerateAccessToken(req, conn, GenerateAccessTokenParams{
User: u,
SessionID: &session.ID,
AuthenticationMethod: models.PasswordGrant,
})
require.NoError(t, err)
require.Equal(t, session.CreatedAt.UTC().Add(allowLowAAL).Unix(), expiresAt)
})

t.Run("AAL2 session for MFA user uses standard JWT expiry", func(t *testing.T) {
models.TruncateAll(conn)

u, err := models.NewUser("", "test2@example.com", "password", "authenticated", nil)
require.NoError(t, err)
require.NoError(t, conn.Create(u))

factor := models.NewFactor(u, "my-totp", models.TOTP, models.FactorStateVerified)
require.NoError(t, conn.Create(factor))
require.NoError(t, conn.Eager().Find(u, u.ID))

session, err := models.NewSession(u.ID, &factor.ID)
require.NoError(t, err)
aal2 := models.AAL2.String()
session.AAL = &aal2
require.NoError(t, conn.Create(session))
require.NoError(t, models.AddClaimToSession(conn, session.ID, models.TOTPSignIn))

cfg := *config
cfg.Sessions.AllowLowAAL = &allowLowAAL

srv := NewService(&cfg, &panicHookManager{})
srv.SetTimeFunc(func() time.Time { return now })

_, expiresAt, err := srv.GenerateAccessToken(req, conn, GenerateAccessTokenParams{
User: u,
SessionID: &session.ID,
AuthenticationMethod: models.PasswordGrant,
})
require.NoError(t, err)
require.Equal(t, now.Add(time.Second*time.Duration(config.JWT.Exp)).Unix(), expiresAt)
})

t.Run("AAL1 session without AllowLowAAL uses standard JWT expiry", func(t *testing.T) {
models.TruncateAll(conn)

u, err := models.NewUser("", "test3@example.com", "password", "authenticated", nil)
require.NoError(t, err)
require.NoError(t, conn.Create(u))

factor := models.NewFactor(u, "my-totp", models.TOTP, models.FactorStateVerified)
require.NoError(t, conn.Create(factor))
require.NoError(t, conn.Eager().Find(u, u.ID))

session, err := models.NewSession(u.ID, nil)
require.NoError(t, err)
require.NoError(t, conn.Create(session))

cfg := *config
cfg.Sessions.AllowLowAAL = nil

srv := NewService(&cfg, &panicHookManager{})
srv.SetTimeFunc(func() time.Time { return now })

_, expiresAt, err := srv.GenerateAccessToken(req, conn, GenerateAccessTokenParams{
User: u,
SessionID: &session.ID,
AuthenticationMethod: models.PasswordGrant,
})
require.NoError(t, err)
require.Equal(t, now.Add(time.Second*time.Duration(config.JWT.Exp)).Unix(), expiresAt)
})
}
Loading