fix: account link proof by auth_time, code in the URL fragment and POST show
- BackendLinkConfirm reads the session's auth_time (bouncer.VerifyAuthTime);
a token without it, or a refreshed old session, answers 409 stale_session
- POST /_user/api/v1/backend-link/show takes the code in the body; GET
/backend-link/{code} stays, deprecated; both answer Cache-Control no-store
- My account answers <frontend_url><page_path>#code=<code>; new key
golem15.user.backend_links.page_path (default /link-admin)
- TestBackendLink now crafts auth_time tokens; TestBackendLinkAuthTimeSmoke
This commit is contained in:
@@ -71,6 +71,27 @@ func BackendLinkRequestTTL(app *backpack.App) (time.Duration, error) {
|
||||
return ttl, nil
|
||||
}
|
||||
|
||||
// DefaultBackendLinkPagePath is the site page the account link points to when
|
||||
// golem15.user.backend_links.page_path is not set.
|
||||
const DefaultBackendLinkPagePath = "/link-admin"
|
||||
|
||||
// BackendLinkPagePath reads golem15.user.backend_links.page_path, the path of
|
||||
// the site page that confirms an account link (default /link-admin). A value
|
||||
// without a leading slash gets one.
|
||||
func BackendLinkPagePath(app *backpack.App) string {
|
||||
if app == nil || app.Config == nil {
|
||||
return DefaultBackendLinkPagePath
|
||||
}
|
||||
path := strings.TrimSpace(app.Config.String("golem15.user.backend_links.page_path"))
|
||||
if path == "" {
|
||||
return DefaultBackendLinkPagePath
|
||||
}
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// FrontendURL is the site the plugin sends people to for pages it does not
|
||||
// serve itself (the account link page): golem15.user.frontend_url, falling
|
||||
// back to app.url, without a trailing slash. Empty when neither is set.
|
||||
@@ -203,14 +224,17 @@ func requestUsable(req *models.BackendLinkRequest, now time.Time) error {
|
||||
// ConfirmLinkRequest stores the link between the signed-in frontend user and
|
||||
// the administrator who created the request, in one transaction: the request
|
||||
// row is locked, must be unused and unexpired, and the session must have
|
||||
// been signed in at or after the request was created (sessionIssuedAt is
|
||||
// the JWT iat, compared in whole seconds because iat has no fraction), so a
|
||||
// browser that was already signed in has to sign in again (D-05). Neither
|
||||
// been signed in at or after the request was created, so a browser that was
|
||||
// already signed in has to sign in again (D-05). sessionAuthTime is the
|
||||
// session token's auth_time claim (bouncer.VerifyAuthTime): the time of the
|
||||
// sign-in itself, which a refresh copies and never renews. It is compared in
|
||||
// whole seconds with the request's created_at because the claim has no
|
||||
// fraction; a zero time is ErrStaleSession. Neither
|
||||
// account may be linked already (ErrAlreadyLinked) and the administrator
|
||||
// must still be activated (ErrBackendUserUnavailable). The request is used
|
||||
// up with the link; BackendLinkedEvent fires inside the transaction and the
|
||||
// two pact.MCPSubjectChanged events after it commits.
|
||||
func (s *BackendLinks) ConfirmLinkRequest(ctx context.Context, plain string, frontendUserID uint, sessionIssuedAt time.Time) (*models.BackendLink, error) {
|
||||
func (s *BackendLinks) ConfirmLinkRequest(ctx context.Context, plain string, frontendUserID uint, sessionAuthTime time.Time) (*models.BackendLink, error) {
|
||||
if !validLinkCode(plain) {
|
||||
return nil, ErrLinkRequestNotFound
|
||||
}
|
||||
@@ -236,7 +260,7 @@ func (s *BackendLinks) ConfirmLinkRequest(ctx context.Context, plain string, fro
|
||||
if err := requestUsable(req, now); err != nil {
|
||||
return err
|
||||
}
|
||||
if sessionIssuedAt.IsZero() || sessionIssuedAt.Before(req.CreatedAt.Truncate(time.Second)) {
|
||||
if sessionAuthTime.IsZero() || sessionAuthTime.Before(req.CreatedAt.Truncate(time.Second)) {
|
||||
return ErrStaleSession
|
||||
}
|
||||
if _, err := activeBackendUser(ctx, tx, req.BackendUserID); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user