Raw
 1package storage
 2
 3import (
 4	"context"
 5	"database/sql"
 6	"encoding/json"
 7	"errors"
 8	"fmt"
 9	"time"
10)
11
12const (
13	PendingUserKey    = "u"
14	PendingBrowserKey = "b"
15)
16
17const (
18	browserLiveRow       = `SELECT browser FROM forward_sessions WHERE id = ? AND user_id = ? AND revoked = 0 AND expires_at > ?`
19	browserRowsRevoke    = `UPDATE forward_sessions SET revoked = 1 WHERE user_id = ? AND revoked = 0 AND (id = ? OR browser = ?)`
20	browserCodesDelete   = `DELETE FROM auth_codes WHERE claims_user_id = ? AND connector_id = ? AND substr(CAST(connector_data AS TEXT), -33) = '.' || ?`
21	browserGrantDelete   = `DELETE FROM auth_requests WHERE connector_id = ? AND substr(CAST(connector_data AS TEXT), 1, length(?) + 1) = ? || '.' AND substr(CAST(connector_data AS TEXT), -33) = '.' || ?`
22	browserPendingDelete = `DELETE FROM auth_requests WHERE connector_id = ?
23  AND (CASE WHEN json_valid(CAST(connector_data AS TEXT)) THEN json_extract(CAST(connector_data AS TEXT), '$.` + PendingUserKey + `') END) = ?
24  AND (CASE WHEN json_valid(CAST(connector_data AS TEXT)) THEN json_extract(CAST(connector_data AS TEXT), '$.` + PendingBrowserKey + `') END) = ?`
25)
26
27func (s *Store) RevokeBrowserAudited(ctx context.Context, userID, sessionID string, audit AppendAuditInput) (Revoked, error) {
28	return s.RevokeBrowserAuditedAt(ctx, userID, sessionID, audit, s.now())
29}
30
31func (s *Store) RevokeBrowserAuditedAt(ctx context.Context, userID, sessionID string, audit AppendAuditInput, now time.Time) (Revoked, error) {
32	const operation = "revoke browser sessions"
33	fields := map[string]any{}
34	if audit.Detail != "" {
35		if err := json.Unmarshal([]byte(audit.Detail), &fields); err != nil || fields == nil {
36			return Revoked{}, invalid(operation, fmt.Errorf("audit detail is not a JSON object"))
37		}
38	}
39	var revoked Revoked
40	err := s.auditedAt(ctx, operation, audit.Actor, now, func(tx *sql.Tx, at time.Time) error {
41		var browser sql.NullString
42		err := tx.QueryRowContext(ctx, browserLiveRow, sessionID, userID, timeToInt(at)).Scan(&browser)
43		switch {
44		case errors.Is(err, sql.ErrNoRows):
45			return invalid(operation, fmt.Errorf("the session is not a live session of the user"))
46		case err != nil:
47			return classify(operation, err)
48		}
49		if revoked.Sessions, err = execCount(ctx, tx, operation, browserRowsRevoke, userID, sessionID, browser); err != nil {
50			return err
51		}
52		if browser.Valid {
53			if revoked.Codes, err = execCount(ctx, tx, operation+" dex auth codes", browserCodesDelete, userID, DexConnectorID, browser.String); err != nil {
54				return err
55			}
56			granted, err := execCount(ctx, tx, operation+" dex spent and logged-in auth requests", browserGrantDelete, DexConnectorID, userID, userID, browser.String)
57			if err != nil {
58				return err
59			}
60			pending, err := execCount(ctx, tx, operation+" dex pending auth requests", browserPendingDelete, DexConnectorID, userID, browser.String)
61			if err != nil {
62				return err
63			}
64			revoked.PendingSignIns = granted + pending
65		}
66		return writeAudit(ctx, tx, operation, audit.Actor, audit.Action, audit.Subject, at, revoked.into(fields))
67	})
68	if err != nil {
69		return Revoked{}, err
70	}
71	return revoked, nil
72}