Files
Livio SpringandCursor 260446f91f Merge commit from fork
* fix: block Login V2 auth for users in deactivated organizations

Enforce organization state on session creation, OIDC token issuance/refresh,
and SAML session creation so deactivated org users cannot authenticate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: reject claim serving for users in deactivated organizations

Require an active resource-owner org in OIDC userinfo and SAML attribute
paths, and use distinct error IDs for inactive user vs inactive org at
token issuance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: drop sessions and tokens when an organization is deactivated

Mirror OrgRemoved cleanup for OrgDeactivated in the V2 session projection
and V1 auth user_session, token, and refresh_token handlers. Also delete
V2 sessions on OrgRemoved, which does not emit per-user removal events.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: reject API tokens from deactivated organizations

Check the caller's resource-owner org state in authz middleware via a
cached OrgByID lookup, so already-issued tokens lose ZITADEL API access
when their organization is deactivated. Return unauthenticated (401).
Target org remains unrestricted so instance admins can still manage
deactivated orgs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: invalidate OIDC refresh tokens after org deactivation

Reject refresh exchange if the user's organization was deactivated
after the refresh token was issued, so grants stay dead after reactivate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: invalidate V2 access tokens after org deactivation

Treat OrgDeactivated after the token position as session termination in
ActiveAccessTokenByToken, so issued ATs stay dead after reactivation.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 06:12:45 +02:00

61 lines
1.6 KiB
Go

package command
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
// checkOrgNotDeactivatedAfter returns an invalid refresh token error if the organization
// was deactivated after the given time. This keeps refresh grants dead after reactivation.
func (c *Commands) checkOrgNotDeactivatedAfter(ctx context.Context, orgID string, after time.Time) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if orgID == "" || after.IsZero() {
return nil
}
model := &orgDeactivatedAfterModel{
orgID: orgID,
after: after,
}
if err = c.eventstore.FilterToQueryReducer(ctx, model); err != nil {
return zerrors.ThrowPreconditionFailed(err, "OIDCS-oR9nD", "Errors.Internal")
}
if model.deactivated {
return zerrors.ThrowPreconditionFailed(nil, "OIDCS-oR9nR", "Errors.OIDCSession.RefreshTokenInvalid")
}
return nil
}
type orgDeactivatedAfterModel struct {
orgID string
after time.Time
events int
deactivated bool
}
func (m *orgDeactivatedAfterModel) Reduce() error {
m.deactivated = m.events > 0
return nil
}
func (m *orgDeactivatedAfterModel) AppendEvents(events ...eventstore.Event) {
m.events += len(events)
}
func (m *orgDeactivatedAfterModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
CreationDateAfter(m.after).
AddQuery().
AggregateTypes(org.AggregateType).
AggregateIDs(m.orgID).
EventTypes(org.OrgDeactivatedEventType).
Builder()
}