Class: CollavreLinear::AuthController

Inherits:
ApplicationController show all
Defined in:
app/controllers/collavre_linear/auth_controller.rb

Instance Method Summary collapse

Instance Method Details

#callbackObject

GET /linear/auth/callback?code=...&state=... Exchange the authorization code, create/update the Account, capture the OAuth app actor id via the GraphQL viewer query.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/controllers/collavre_linear/auth_controller.rb', line 13

def callback
  expected_state = session.delete(:linear_oauth_state)
  unless params[:state].present? && params[:state] == expected_state
    redirect_to collavre.creatives_path,
                alert: I18n.t("collavre_linear.auth.invalid_state",
                              default: "Invalid OAuth state. Please try connecting again.")
    return
  end

  tokens = OAuthTokenService.exchange(params[:code])

  # Build a temporary account so we can call the viewer query
   = CollavreLinear::Account.new(
    access_token: tokens[:access_token]
  )
  client = CollavreLinear::Client.new()
  viewer = client.viewer_and_app_actor

  # Never reassign a Linear identity already owned by another Collavre user
  # (that would steal their account + project links). Bind by the CURRENT
  # user instead (user_id is unique — one Linear account per user), so a
  # reconnect with a different Linear UID updates in place rather than
  # violating the unique index.
  if CollavreLinear::Account.where(linear_uid: viewer[:user_id])
                            .where.not(user_id: Current.user.id).exists?
    redirect_to collavre.creatives_path,
                alert: I18n.t("collavre_linear.auth.already_linked_other")
    return
  end

   = CollavreLinear::Account.find_or_initialize_by(
    user_id: Current.user.id
  )
  .linear_uid       = viewer[:user_id]
  .access_token     = tokens[:access_token]
  .refresh_token    = tokens[:refresh_token]
  .token_expires_at = Time.current + tokens[:expires_in].to_i.seconds if tokens[:expires_in]
  .app_actor_id     = viewer[:app_actor_id]
  .workspace_id     = viewer[:organization_id]
  .save!

  creative_id = session.delete(:linear_creative_id)
  if creative_id.present?
    # Redirect to the MOUNTED path (/linear/auth/setup). The engine is
    # detached, so we fold its mount prefix in explicitly via `script_name:`
    # — the bare engine-internal /auth/setup would 404 in the popup.
    redirect_to linear_engine.setup_auth_path(
      creative_id: creative_id,
      script_name: CollavreLinear::Engine::MOUNT_PATH
    )
  else
    redirect_to collavre.creatives_path,
                notice: I18n.t("collavre_linear.auth.connected", default: "Linear connected.")
  end
end

#setupObject

GET /linear/auth/setup?creative_id=... Setup wizard entry point — mirrors the collavre_github pattern.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'app/controllers/collavre_linear/auth_controller.rb', line 71

def setup
  @creative_id = params[:creative_id]

  unless @creative_id.present?
    render plain: I18n.t("collavre_linear.integration.missing_creative",
                         default: "creative_id is required"),
           status: :bad_request
    return
  end

  @creative = Collavre::Creative.find_by(id: @creative_id)
  unless @creative
    render plain: I18n.t("collavre_linear.integration.missing_creative",
                         default: "Creative not found"),
           status: :not_found
    return
  end

  unless @creative.has_permission?(Current.user, :admin)
    render plain: I18n.t("collavre_linear.errors.forbidden",
                         default: "Access denied"),
           status: :forbidden
    return
  end

  render layout: false
end

#store_creativeObject

POST /linear/auth/store_creative Persist creative_id and generate a CSRF state token in the session, then redirect the popup window straight to Linear's OAuth screen. The form POSTs into a popup with no JS handler, so returning JSON here would just render raw JSON in the popup and never start OAuth — a 303 redirect makes the popup follow through to Linear.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'app/controllers/collavre_linear/auth_controller.rb', line 105

def store_creative
  missing = OAuthTokenService.missing_config
  if missing.any?
    render plain: I18n.t("collavre_linear.auth.oauth_config_missing",
                         keys: missing.join(", ")),
           status: :service_unavailable
    return
  end

  session[:linear_creative_id] = params[:creative_id]
  state = SecureRandom.hex(24)
  session[:linear_oauth_state] = state
  authorize_url = OAuthTokenService.authorize_url(state: state, creative_id: params[:creative_id])
  redirect_to authorize_url, allow_other_host: true, status: :see_other
end