feat(login): reuse existing session if no prompt is provided and only single session exists (#6272)

* feat: reuse existing session if no prompt is provided and only single session exists

* fix tests
This commit is contained in:
Livio Spring 2023-08-01 13:21:44 +02:00 committed by GitHub
parent 782f7ad647
commit dd480f8a8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 143 additions and 10 deletions

View File

@ -921,11 +921,15 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
steps = append(steps, new(domain.ExternalNotFoundOptionStep))
return steps, nil
}
steps = append(steps, new(domain.LoginStep))
if domain.IsPrompt(request.Prompt, domain.PromptCreate) {
return append(steps, &domain.RegistrationStep{}), nil
}
if len(request.Prompt) == 0 || domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) {
// if there's a login or consent prompt, but not select account, just return the login step
if len(request.Prompt) > 0 && !domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) {
return append(steps, new(domain.LoginStep)), nil
} else {
// if no user was specified, no prompt or select_account was provided,
// then check the active user sessions (of the user agent)
users, err := repo.usersForUserSelection(request)
if err != nil {
return nil, err
@ -936,11 +940,19 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
if request.SelectedIDPConfigID != "" {
steps = append(steps, &domain.RedirectToExternalIDPStep{})
}
if len(request.Prompt) == 0 && len(users) > 0 {
if len(request.Prompt) == 0 && len(users) == 0 {
steps = append(steps, new(domain.LoginStep))
}
// if no prompt was provided, but there are multiple user sessions, then the user must decide which to use
if len(request.Prompt) == 0 && len(users) > 1 {
steps = append(steps, &domain.SelectUserStep{Users: users})
}
if len(steps) > 0 {
return steps, nil
}
// a single user session was found, use that automatically
request.UserID = users[0].UserID
}
return steps, nil
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID, request.LoginPolicy.IgnoreUnknownUsernames)
if err != nil {

View File

@ -296,6 +296,28 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
[]domain.NextStep{&domain.RedirectToCallbackStep{}},
nil,
},
{
"user not set prompt create, registration step",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&domain.AuthRequest{
Prompt: []domain.Prompt{domain.PromptCreate},
}, false},
[]domain.NextStep{&domain.RegistrationStep{}},
nil,
},
{
"user not set, prompts other than select account, create step",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&domain.AuthRequest{
Prompt: []domain.Prompt{domain.PromptLogin, domain.PromptConsent},
}, false},
[]domain.NextStep{&domain.LoginStep{}},
nil,
},
{
"user not set no active session, login step",
fields{
@ -333,7 +355,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
errors.IsInternal,
},
{
"user not set, prompt select account, login and select account steps",
"user not set, prompt select account, select account step",
fields{
userSessionViewProvider: &mockViewUserSession{
Users: []mockUser{
@ -353,7 +375,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}}, false},
[]domain.NextStep{
&domain.LoginStep{},
&domain.SelectUserStep{
Users: []domain.UserSelection{
{
@ -373,7 +394,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil,
},
{
"user not set, primary domain set, prompt select account, login and select account steps",
"user not set, primary domain set, prompt select account, select account step",
fields{
userSessionViewProvider: &mockViewUserSession{
Users: []mockUser{
@ -393,7 +414,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}, RequestedOrgID: "orgID1"}, false},
[]domain.NextStep{
&domain.LoginStep{},
&domain.SelectUserStep{
Users: []domain.UserSelection{
{
@ -407,7 +427,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil,
},
{
"user not set, prompt select account, no active session, login and select account steps",
"user not set, prompt select account, no active session, select account step",
fields{
userSessionViewProvider: &mockViewUserSession{
Users: nil,
@ -416,12 +436,113 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}}, false},
[]domain.NextStep{
&domain.LoginStep{},
&domain.SelectUserStep{
Users: []domain.UserSelection{},
}},
nil,
},
{
"user not set single active session, callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().Add(-5 * time.Minute),
Users: []mockUser{
{
"id1",
"loginname1",
"orgID1",
},
},
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MFAMaxSetUp: int32(domain.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
userGrantProvider: &mockUserGrants{},
projectProvider: &mockProject{},
applicationProvider: &mockApp{app: &query.App{OIDCConfig: &query.OIDCApp{AppType: domain.OIDCApplicationTypeWeb}}},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{
ShowFailures: true,
},
},
idpUserLinksProvider: &mockIDPUserLinks{},
},
args{&domain.AuthRequest{
Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
},
}, false},
[]domain.NextStep{&domain.RedirectToCallbackStep{}},
nil,
},
{
"user not set multiple active sessions, select account step",
fields{
userSessionViewProvider: &mockViewUserSession{
Users: []mockUser{
{
"id1",
"loginname1",
"orgID1",
},
{
"id2",
"loginname2",
"orgID2",
},
},
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MFAMaxSetUp: int32(domain.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
userGrantProvider: &mockUserGrants{},
projectProvider: &mockProject{},
applicationProvider: &mockApp{app: &query.App{OIDCConfig: &query.OIDCApp{AppType: domain.OIDCApplicationTypeWeb}}},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{
ShowFailures: true,
},
},
idpUserLinksProvider: &mockIDPUserLinks{},
},
args{&domain.AuthRequest{
Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
},
}, false},
[]domain.NextStep{&domain.SelectUserStep{
Users: []domain.UserSelection{
{
UserID: "id1",
LoginName: "loginname1",
SelectionPossible: true,
ResourceOwner: "orgID1",
},
{
UserID: "id2",
LoginName: "loginname2",
SelectionPossible: true,
ResourceOwner: "orgID2",
},
},
}},
nil,
},
{
"user not found, not found error",
fields{