Skip to content

Commit

Permalink
Support a new settings action delete_account
Browse files Browse the repository at this point in the history
ref DEV-1232
  • Loading branch information
louischan-oursky committed Jul 3, 2024
2 parents 47de891 + 719d05d commit f8f9db0
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 96 deletions.
2 changes: 1 addition & 1 deletion docs/specs/oidc.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ Supported values: `login`, `signup`

When it is specified, the user will be redirected to the corresponding auth ui pages of the settings action. After completing the action, the user will be redirected back to the app through redirect URI.

Supported values: `change_password`.
Supported values: `change_password`, `delete_account`.

### x_authentication_flow_group

Expand Down
6 changes: 5 additions & 1 deletion pkg/auth/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ var DependencySet = wire.NewSet(

wire.Bind(new(webapp.SessionMiddlewareOAuthSessionService), new(*oauthsession.StoreRedis)),
wire.Bind(new(webapp.SessionMiddlewareUIInfoResolver), new(*oidc.UIInfoResolver)),
wire.Bind(new(handlerwebapp.SettingsDeleteAccountSuccessUIInfoResolver), new(*oidc.UIInfoResolver)),
wire.Bind(new(webapp.UIInfoResolver), new(*oidc.UIInfoResolver)),
wire.Bind(new(webapp.GraphService), new(*interaction.Service)),
wire.Bind(new(webapp.CookieManager), new(*httputil.CookieManager)),
Expand Down Expand Up @@ -101,7 +102,8 @@ var DependencySet = wire.NewSet(

wire.Bind(new(handlerwebapp.SelectAccountAuthenticationInfoService), new(*authenticationinfo.StoreRedis)),
wire.Bind(new(handlerwebappauthflowv2.SelectAccountAuthenticationInfoService), new(*authenticationinfo.StoreRedis)),

wire.Bind(new(handlerwebapp.SettingsDeleteAccountSuccessAuthenticationInfoService), new(*authenticationinfo.StoreRedis)),
wire.Bind(new(handlerwebapp.SettingsDeleteAccountAuthenticationInfoService), new(*authenticationinfo.StoreRedis)),
wire.Bind(new(handlerwebapp.SetupTOTPEndpointsProvider), new(*endpoints.Endpoints)),
wire.Bind(new(handlerwebapp.OAuthEntrypointEndpointsProvider), new(*endpoints.Endpoints)),
wire.Bind(new(handlerwebapp.ConfirmTerminateOtherSessionsEndpointsProvider), new(*endpoints.Endpoints)),
Expand Down Expand Up @@ -174,6 +176,7 @@ var DependencySet = wire.NewSet(
handlerwebapp.DependencySet,
wire.Bind(new(handlerwebapp.AuthflowControllerOAuthClientResolver), new(*oauthclient.Resolver)),
wire.Bind(new(handlerwebapp.AuthflowControllerSessionStore), new(*webapp.SessionStoreRedis)),
wire.Bind(new(handlerwebapp.SettingsDeleteAccountSessionStore), new(*webapp.SessionStoreRedis)),
wire.Bind(new(handlerwebapp.SettingsAuthenticatorService), new(*authenticatorservice.Service)),
wire.Bind(new(handlerwebapp.SettingsMFAService), new(*mfa.Service)),
wire.Bind(new(handlerwebapp.SettingsIdentityService), new(*identityservice.Service)),
Expand All @@ -183,6 +186,7 @@ var DependencySet = wire.NewSet(
wire.Bind(new(handlerwebapp.SettingsProfileEditStdAttrsService), new(*featurestdattrs.Service)),
wire.Bind(new(handlerwebapp.SettingsProfileEditCustomAttrsService), new(*featurecustomattrs.Service)),
wire.Bind(new(handlerwebapp.SettingsDeleteAccountUserService), new(*facade.UserFacade)),
wire.Bind(new(handlerwebapp.SettingsDeleteAccountOAuthSessionService), new(*oauthsession.StoreRedis)),
wire.Bind(new(handlerwebapp.SettingsAuthorizationService), new(*oauth.AuthorizationService)),
wire.Bind(new(handlerwebapp.SettingsSessionListingService), new(*sessionlisting.SessionListingService)),
wire.Bind(new(handlerwebapp.EnterLoginIDService), new(*identityservice.Service)),
Expand Down
63 changes: 56 additions & 7 deletions pkg/auth/handler/webapp/settings_delete_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"github.com/authgear/authgear-server/pkg/api/apierrors"
"github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels"
"github.com/authgear/authgear-server/pkg/auth/webapp"
"github.com/authgear/authgear-server/pkg/lib/authn/authenticationinfo"
"github.com/authgear/authgear-server/pkg/lib/config"
"github.com/authgear/authgear-server/pkg/lib/oauth/oauthsession"
"github.com/authgear/authgear-server/pkg/lib/session"
"github.com/authgear/authgear-server/pkg/lib/successpage"
"github.com/authgear/authgear-server/pkg/util/clock"
Expand All @@ -34,14 +36,33 @@ type SettingsDeleteAccountUserService interface {
ScheduleDeletionByEndUser(userID string) error
}

type SettingsDeleteAccountOAuthSessionService interface {
Get(entryID string) (*oauthsession.Entry, error)
Save(entry *oauthsession.Entry) error
}

type SettingsDeleteAccountSessionStore interface {
Create(session *webapp.Session) (err error)
Delete(id string) (err error)
Update(session *webapp.Session) (err error)
}

type SettingsDeleteAccountAuthenticationInfoService interface {
Save(entry *authenticationinfo.Entry) (err error)
}

type SettingsDeleteAccountHandler struct {
ControllerFactory ControllerFactory
BaseViewModel *viewmodels.BaseViewModeler
Renderer Renderer
AccountDeletion *config.AccountDeletionConfig
Clock clock.Clock
Users SettingsDeleteAccountUserService
Cookies CookieManager
ControllerFactory ControllerFactory
BaseViewModel *viewmodels.BaseViewModeler
Renderer Renderer
AccountDeletion *config.AccountDeletionConfig
Clock clock.Clock
Users SettingsDeleteAccountUserService
Cookies CookieManager
OAuthSessions SettingsDeleteAccountOAuthSessionService
Sessions SettingsDeleteAccountSessionStore
SessionCookie webapp.SessionCookieDef
AuthenticationInfoService SettingsDeleteAccountAuthenticationInfoService
}

func (h *SettingsDeleteAccountHandler) GetData(r *http.Request, rw http.ResponseWriter) (map[string]interface{}, error) {
Expand Down Expand Up @@ -70,6 +91,7 @@ func (h *SettingsDeleteAccountHandler) ServeHTTP(w http.ResponseWriter, r *http.

currentSession := session.GetSession(r.Context())
redirectURI := "/settings/delete_account/success"
webSession := webapp.GetSession(r.Context())

ctrl.Get(func() error {
data, err := h.GetData(r, w)
Expand Down Expand Up @@ -97,6 +119,33 @@ func (h *SettingsDeleteAccountHandler) ServeHTTP(w http.ResponseWriter, r *http.
return err
}

if webSession != nil && webSession.OAuthSessionID != "" {
// delete account triggered by sdk via settings action
// handle settings action result here

authInfoEntry := authenticationinfo.NewEntry(currentSession.GetAuthenticationInfo(), webSession.OAuthSessionID)
err := h.AuthenticationInfoService.Save(authInfoEntry)
if err != nil {
return err
}
webSession.Extra["authentication_info_id"] = authInfoEntry.ID
err = h.Sessions.Update(webSession)
if err != nil {
return err
}

entry, err := h.OAuthSessions.Get(webSession.OAuthSessionID)
if err != nil {
return err
}

entry.T.SettingsActionResult = oauthsession.NewSettingsActionResult()
err = h.OAuthSessions.Save(entry)
if err != nil {
return err
}
}

// set success page path cookie before visiting success page
result := webapp.Result{
RedirectURI: redirectURI,
Expand Down
48 changes: 42 additions & 6 deletions pkg/auth/handler/webapp/settings_delete_account_success.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"net/http"

"github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels"
"github.com/authgear/authgear-server/pkg/auth/webapp"
"github.com/authgear/authgear-server/pkg/lib/authn/authenticationinfo"
"github.com/authgear/authgear-server/pkg/lib/config"
"github.com/authgear/authgear-server/pkg/util/clock"
"github.com/authgear/authgear-server/pkg/util/httproute"
Expand All @@ -17,16 +19,26 @@ var TemplateWebSettingsDeleteAccountSuccessHTML = template.RegisterHTML(

func ConfigureSettingsDeleteAccountSuccessRoute(route httproute.Route) httproute.Route {
return route.
WithMethods("GET").
WithMethods("OPTIONS", "POST", "GET").
WithPathPattern("/settings/delete_account/success")
}

type SettingsDeleteAccountSuccessUIInfoResolver interface {
SetAuthenticationInfoInQuery(redirectURI string, e *authenticationinfo.Entry) string
}

type SettingsDeleteAccountSuccessAuthenticationInfoService interface {
Get(entryID string) (entry *authenticationinfo.Entry, err error)
}

type SettingsDeleteAccountSuccessHandler struct {
ControllerFactory ControllerFactory
BaseViewModel *viewmodels.BaseViewModeler
Renderer Renderer
AccountDeletion *config.AccountDeletionConfig
Clock clock.Clock
ControllerFactory ControllerFactory
BaseViewModel *viewmodels.BaseViewModeler
Renderer Renderer
AccountDeletion *config.AccountDeletionConfig
Clock clock.Clock
UIInfoResolver SettingsDeleteAccountSuccessUIInfoResolver
AuthenticationInfoService SettingsDeleteAccountSuccessAuthenticationInfoService
}

func (h *SettingsDeleteAccountSuccessHandler) GetData(r *http.Request, rw http.ResponseWriter) (map[string]interface{}, error) {
Expand All @@ -53,6 +65,8 @@ func (h *SettingsDeleteAccountSuccessHandler) ServeHTTP(w http.ResponseWriter, r
}
defer ctrl.Serve()

webSession := webapp.GetSession(r.Context())

ctrl.Get(func() error {
data, err := h.GetData(r, w)
if err != nil {
Expand All @@ -62,4 +76,26 @@ func (h *SettingsDeleteAccountSuccessHandler) ServeHTTP(w http.ResponseWriter, r
h.Renderer.RenderHTML(w, r, TemplateWebSettingsDeleteAccountSuccessHTML, data)
return nil
})

ctrl.PostAction("", func() error {
redirectURI := ""
if webSession != nil && webSession.RedirectURI != "" {
// delete account triggered by sdk via settings action
// redirect to oauth callback
redirectURI = webSession.RedirectURI
if authInfoID, ok := webSession.Extra["authentication_info_id"].(string); ok {
authInfo, err := h.AuthenticationInfoService.Get(authInfoID)
if err != nil {
return err
}
redirectURI = h.UIInfoResolver.SetAuthenticationInfoInQuery(redirectURI, authInfo)
}
}

result := webapp.Result{
RedirectURI: redirectURI,
}
result.WriteResponse(w, r)
return nil
})
}
32 changes: 18 additions & 14 deletions pkg/auth/webapp/success_page_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,25 @@ func (m *SuccessPageMiddleware) Pop(r *http.Request, rw http.ResponseWriter) str
// the cookie should be set right before redirecting to the success page
func (m *SuccessPageMiddleware) Handle(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
currentPath := r.URL.Path
pathInCookie := m.Pop(r, w)

if currentPath != pathInCookie {
// Show invalid session error when the path cookie doesn't match
// the current path
apierror := apierrors.AsAPIError(ErrInvalidSession)
errorCookie, err := m.ErrorCookie.SetRecoverableError(r, apierror)
if err != nil {
panic(err)
// We want to allow POST in success page.
// For example, POST in delete account success page to finish settings action.
if r.Method == "GET" {
currentPath := r.URL.Path
pathInCookie := m.Pop(r, w)
if currentPath != pathInCookie {
// Show invalid session error when the path cookie doesn't match
// the current path
apierror := apierrors.AsAPIError(ErrInvalidSession)
errorCookie, err := m.ErrorCookie.SetRecoverableError(r, apierror)
if err != nil {
panic(err)
}
httputil.UpdateCookie(w, errorCookie)
http.Redirect(w, r, m.Endpoints.ErrorEndpointURL(m.UIConfig.Implementation).Path, http.StatusFound)
return
}
httputil.UpdateCookie(w, errorCookie)
http.Redirect(w, r, m.Endpoints.ErrorEndpointURL(m.UIConfig.Implementation).Path, http.StatusFound)
} else {
next.ServeHTTP(w, r)
}

next.ServeHTTP(w, r)
})
}
34 changes: 22 additions & 12 deletions pkg/auth/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/lib/endpoints/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ func (e *Endpoints) SettingsChangePasswordURL() *url.URL {
return e.urlOf("settings/change_password")
}

func (e *Endpoints) SettingsDeleteAccountURL() *url.URL {
return e.urlOf("settings/delete_account")
}

func (e *Endpoints) SSOCallbackURL(alias string) *url.URL {
u := e.SSOCallbackEndpointURL()
u.Path = path.Join(u.Path, url.PathEscape(alias))
Expand Down
26 changes: 16 additions & 10 deletions pkg/lib/oauth/handler/handler_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type UIInfoResolver interface {

type UIURLBuilder interface {
BuildAuthenticationURL(client *config.OAuthClientConfig, r protocol.AuthorizationRequest, e *oauthsession.Entry) (*url.URL, error)
BuildSettingsActionURL(client *config.OAuthClientConfig, r protocol.AuthorizationRequest, e *oauthsession.Entry, redirectURI *url.URL) (*url.URL, error)
BuildSettingsActionURL(client *config.OAuthClientConfig, r protocol.AuthorizationRequest, e *oauthsession.Entry) (*url.URL, error)
}

type AppSessionTokenService interface {
Expand Down Expand Up @@ -94,14 +94,15 @@ func NewAuthorizationHandlerLogger(lf *log.Factory) AuthorizationHandlerLogger {
}

type AuthorizationHandler struct {
Context context.Context
AppID config.AppID
Config *config.OAuthConfig
HTTPConfig *config.HTTPConfig
HTTPProto httputil.HTTPProto
HTTPOrigin httputil.HTTPOrigin
AppDomains config.AppDomains
Logger AuthorizationHandlerLogger
Context context.Context
AppID config.AppID
Config *config.OAuthConfig
AccountDeletionConfig *config.AccountDeletionConfig
HTTPConfig *config.HTTPConfig
HTTPProto httputil.HTTPProto
HTTPOrigin httputil.HTTPOrigin
AppDomains config.AppDomains
Logger AuthorizationHandlerLogger

UIURLBuilder UIURLBuilder
UIInfoResolver UIInfoResolver
Expand Down Expand Up @@ -391,7 +392,7 @@ func (h *AuthorizationHandler) doHandle(
}

if r.ResponseType() == string(SettingsActonResponseType) {
redirectURI, err = h.UIURLBuilder.BuildSettingsActionURL(client, r, oauthSessionEntry, redirectURI)
redirectURI, err = h.UIURLBuilder.BuildSettingsActionURL(client, r, oauthSessionEntry)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -592,6 +593,11 @@ func (h *AuthorizationHandler) validateRequest(

switch r.ResponseType() {
case SettingsActonResponseType:
if r.SettingsAction() == "delete_account" {
if !h.AccountDeletionConfig.ScheduledByEndUserEnabled {
return protocol.NewError("invalid_request", "account deletion by end user is disabled")
}
}
fallthrough
case CodeResponseType:
if client.IsPublic() {
Expand Down
Loading

0 comments on commit f8f9db0

Please sign in to comment.