-
Notifications
You must be signed in to change notification settings - Fork 940
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: trusted devices and 'remember me'
- Loading branch information
Showing
41 changed files
with
681 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
backend/flow_api/flow/credential_usage/action_remember_me.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package credential_usage | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/teamhanko/hanko/backend/flow_api/flow/shared" | ||
"github.com/teamhanko/hanko/backend/flowpilot" | ||
) | ||
|
||
type RememberMe struct { | ||
shared.Action | ||
} | ||
|
||
func (a RememberMe) GetName() flowpilot.ActionName { | ||
return shared.ActionRememberMe | ||
} | ||
|
||
func (a RememberMe) GetDescription() string { | ||
return "Enables the user to stay signed in." | ||
} | ||
|
||
func (a RememberMe) Initialize(c flowpilot.InitializationContext) { | ||
deps := a.GetDeps(c) | ||
|
||
c.AddInputs(flowpilot.BooleanInput("remember_me").Required(true)) | ||
|
||
if deps.Cfg.Session.Cookie.Retention != "prompt" { | ||
c.SuspendAction() | ||
} | ||
} | ||
|
||
func (a RememberMe) Execute(c flowpilot.ExecutionContext) error { | ||
if valid := c.ValidateInputData(); !valid { | ||
return c.Error(flowpilot.ErrorFormDataInvalid) | ||
} | ||
|
||
rememberMeSelected := c.Input().Get("remember_me").Bool() | ||
|
||
if err := c.Stash().Set(shared.StashPathRememberMeSelected, rememberMeSelected); err != nil { | ||
return fmt.Errorf("failed to set remember_me_selected to stash: %w", err) | ||
} | ||
|
||
return c.Continue(c.GetCurrentState()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package device_trust | ||
|
||
import ( | ||
"fmt" | ||
"github.com/teamhanko/hanko/backend/flow_api/flow/shared" | ||
"github.com/teamhanko/hanko/backend/flowpilot" | ||
) | ||
|
||
type TrustDevice struct { | ||
shared.Action | ||
} | ||
|
||
func (a TrustDevice) GetName() flowpilot.ActionName { | ||
return shared.ActionTrustDevice | ||
} | ||
|
||
func (a TrustDevice) GetDescription() string { | ||
return "Trust this device, to skip MFA on subsequent logins." | ||
} | ||
|
||
func (a TrustDevice) Initialize(c flowpilot.InitializationContext) {} | ||
|
||
func (a TrustDevice) Execute(c flowpilot.ExecutionContext) error { | ||
if err := c.Stash().Set(shared.StashPathDeviceTrustGranted, true); err != nil { | ||
return fmt.Errorf("failed to set device_trust_granted to the stash: %w", err) | ||
} | ||
|
||
c.PreventRevert() | ||
|
||
return c.Continue() | ||
} |
67 changes: 67 additions & 0 deletions
67
backend/flow_api/flow/device_trust/hook_issue_trust_device_cookie.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package device_trust | ||
|
||
import ( | ||
"fmt" | ||
"github.com/gofrs/uuid" | ||
"github.com/teamhanko/hanko/backend/flow_api/flow/shared" | ||
"github.com/teamhanko/hanko/backend/flow_api/services" | ||
"github.com/teamhanko/hanko/backend/flowpilot" | ||
"net/http" | ||
) | ||
|
||
type IssueTrustDeviceCookie struct { | ||
shared.Action | ||
} | ||
|
||
func (h IssueTrustDeviceCookie) Execute(c flowpilot.HookExecutionContext) error { | ||
var err error | ||
|
||
deps := h.GetDeps(c) | ||
|
||
if deps.Cfg.MFA.DeviceTrustPolicy == "never" || | ||
(deps.Cfg.MFA.DeviceTrustPolicy == "prompt" && !c.Stash().Get(shared.StashPathDeviceTrustGranted).Bool()) { | ||
return nil | ||
} | ||
|
||
if !c.Stash().Get(shared.StashPathUserID).Exists() { | ||
return fmt.Errorf("user id does not exist in the stash") | ||
} | ||
|
||
userID, err := uuid.FromString(c.Stash().Get(shared.StashPathUserID).String()) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse stashed user_id into a uuid: %w", err) | ||
} | ||
|
||
deviceTrustService := services.DeviceTrustService{ | ||
Persister: deps.Persister.GetTrustedDevicePersisterWithConnection(deps.Tx), | ||
Cfg: deps.Cfg, | ||
HttpContext: deps.HttpContext, | ||
} | ||
|
||
deviceToken, err := deviceTrustService.GenerateRandomToken(62) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate trusted device token: %w", err) | ||
} | ||
|
||
name := deps.Cfg.MFA.DeviceTrustCookieName | ||
maxAge := int(deps.Cfg.MFA.DeviceTrustDuration.Seconds()) | ||
|
||
if maxAge > 0 { | ||
err = deviceTrustService.CreateTrustedDevice(userID, deviceToken) | ||
if err != nil { | ||
return fmt.Errorf("failed to storer trusted device: %w", err) | ||
} | ||
} | ||
|
||
cookie := new(http.Cookie) | ||
cookie.Name = name | ||
cookie.Value = deviceToken | ||
cookie.Path = "/" | ||
cookie.HttpOnly = true | ||
cookie.Secure = true | ||
cookie.MaxAge = maxAge | ||
|
||
deps.HttpContext.SetCookie(cookie) | ||
|
||
return nil | ||
} |
43 changes: 43 additions & 0 deletions
43
backend/flow_api/flow/device_trust/hook_schedule_trust_device_state.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package device_trust | ||
|
||
import ( | ||
"github.com/gofrs/uuid" | ||
"github.com/teamhanko/hanko/backend/flow_api/flow/shared" | ||
"github.com/teamhanko/hanko/backend/flow_api/services" | ||
"github.com/teamhanko/hanko/backend/flowpilot" | ||
) | ||
|
||
type ScheduleTrustDeviceState struct { | ||
shared.Action | ||
} | ||
|
||
func (h ScheduleTrustDeviceState) Execute(c flowpilot.HookExecutionContext) error { | ||
deps := h.GetDeps(c) | ||
|
||
if !deps.Cfg.MFA.Enabled || deps.Cfg.MFA.DeviceTrustPolicy != "prompt" { | ||
return nil | ||
} | ||
|
||
if c.IsFlow(shared.FlowLogin) && c.Stash().Get(shared.StashPathLoginMethod).String() == "passkey" { | ||
return nil | ||
} | ||
|
||
if !c.Stash().Get(shared.StashPathUserHasSecurityKey).Bool() && | ||
!c.Stash().Get(shared.StashPathUserHasOTPSecret).Bool() { | ||
return nil | ||
} | ||
|
||
deviceTrustService := services.DeviceTrustService{ | ||
Persister: deps.Persister.GetTrustedDevicePersisterWithConnection(deps.Tx), | ||
Cfg: deps.Cfg, | ||
HttpContext: deps.HttpContext, | ||
} | ||
|
||
userID := uuid.FromStringOrNil(c.Stash().Get(shared.StashPathUserID).String()) | ||
|
||
if !deviceTrustService.CheckDeviceTrust(userID) { | ||
c.ScheduleStates(shared.StateDeviceTrust) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.