o_auth_controller.ex 20.6 KB
Newer Older
1
# Pleroma: A lightweight social networking server
Haelwenn's avatar
Haelwenn committed
2
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3
4
# SPDX-License-Identifier: AGPL-3.0-only

5
6
7
defmodule Pleroma.Web.OAuth.OAuthController do
  use Pleroma.Web, :controller

8
  alias Pleroma.Helpers.AuthHelper
9
  alias Pleroma.Helpers.UriHelper
10
  alias Pleroma.Maps
lain's avatar
lain committed
11
  alias Pleroma.MFA
12
  alias Pleroma.Registration
13
14
  alias Pleroma.Repo
  alias Pleroma.User
15
  alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
16
  alias Pleroma.Web.OAuth.App
Haelwenn's avatar
Haelwenn committed
17
  alias Pleroma.Web.OAuth.Authorization
18
  alias Pleroma.Web.OAuth.MFAController
19
  alias Pleroma.Web.OAuth.MFAView
lain's avatar
lain committed
20
  alias Pleroma.Web.OAuth.OAuthView
Haelwenn's avatar
Haelwenn committed
21
  alias Pleroma.Web.OAuth.Scopes
Haelwenn's avatar
Haelwenn committed
22
  alias Pleroma.Web.OAuth.Token
Maksim's avatar
Maksim committed
23
24
  alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
  alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
25
  alias Pleroma.Web.Plugs.RateLimiter
26
  alias Pleroma.Web.Utils.Params
27

28
29
  require Logger

30
  if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
31

lain's avatar
lain committed
32
33
  plug(:fetch_session)
  plug(:fetch_flash)
34

35
36
  plug(:skip_plug, [
    Pleroma.Web.Plugs.OAuthScopesPlug,
37
    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
38
  ])
39
40

  plug(RateLimiter, [name: :authentication] when action == :create_authorization)
41

lain's avatar
lain committed
42
  action_fallback(Pleroma.Web.OAuth.FallbackController)
43

44
45
  @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"

46
  # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
47
  def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
48
49
50
51
    {auth_attrs, params} = Map.pop(params, "authorization")
    authorize(conn, Map.merge(params, auth_attrs))
  end

52
  def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
53
    if Params.truthy_param?(params["force_login"]) do
54
55
      do_authorize(conn, params)
    else
56
      handle_existing_authorization(conn, params)
57
58
59
    end
  end

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  # Note: the token is set in oauth_plug, but the token and client do not always go together.
  # For example, MastodonFE's token is set if user requests with another client,
  # after user already authorized to MastodonFE.
  # So we have to check client and token.
  def authorize(
        %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
        %{"client_id" => client_id} = params
      ) do
    with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
         ^client_id <- t.app.client_id do
      handle_existing_authorization(conn, params)
    else
      _ -> do_authorize(conn, params)
    end
  end

76
  def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
77

78
  defp do_authorize(%Plug.Conn{} = conn, params) do
79
    app = Repo.get_by(App, client_id: params["client_id"])
80
    available_scopes = (app && app.scopes) || []
81
    scopes = Scopes.fetch_scopes(params, available_scopes)
82

83
84
85
86
87
88
89
    user =
      with %{assigns: %{user: %User{} = user}} <- conn do
        user
      else
        _ -> nil
      end

90
91
92
93
94
95
96
    scopes =
      if scopes == [] do
        available_scopes
      else
        scopes
      end

97
    # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
98
    render(conn, Authenticator.auth_template(), %{
99
100
      user: user,
      app: app && Map.delete(app, :client_secret),
101
102
      response_type: params["response_type"],
      client_id: params["client_id"],
103
104
      available_scopes: available_scopes,
      scopes: scopes,
105
106
107
      redirect_uri: params["redirect_uri"],
      state: params["state"],
      params: params
lain's avatar
lain committed
108
    })
109
110
  end

111
112
  defp handle_existing_authorization(
         %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
113
         %{"redirect_uri" => @oob_token_redirect_uri}
114
       ) do
115
116
117
118
119
120
121
122
    render(conn, "oob_token_exists.html", %{token: token})
  end

  defp handle_existing_authorization(
         %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
         %{} = params
       ) do
    app = Repo.preload(token, :app).app
123
124
125
126
127

    redirect_uri =
      if is_binary(params["redirect_uri"]) do
        params["redirect_uri"]
      else
128
        default_redirect_uri(app)
129
130
      end

131
132
    if redirect_uri in String.split(app.redirect_uris) do
      redirect_uri = redirect_uri(conn, redirect_uri)
133
      url_params = %{access_token: token.token}
134
      url_params = Maps.put_if_present(url_params, :state, params["state"])
135
      url = UriHelper.modify_uri_params(redirect_uri, url_params)
136
      redirect(conn, external: url)
137
138
    else
      conn
139
      |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
140
      |> redirect(external: redirect_uri(conn, redirect_uri))
141
142
143
    end
  end

144
145
146
147
148
149
150
  def create_authorization(_, _, opts \\ [])

  def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, params, []) do
    create_authorization(conn, params, user: user)
  end

  def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
151
152
    with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
         {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
153
      after_create_authorization(conn, auth, params)
154
155
    else
      error ->
156
        handle_create_authorization_error(conn, error, params)
157
158
    end
  end
lain's avatar
lain committed
159

160
161
162
  def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
        "authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
      }) do
163
164
165
166
    # Enforcing the view to reuse the template when calling from other controllers
    conn
    |> put_view(OAuthView)
    |> render("oob_authorization_created.html", %{auth: auth})
167
168
  end

169
  def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
170
171
        "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
      }) do
172
    app = Repo.preload(auth, :app).app
lain's avatar
lain committed
173

Ivan Tashkinov's avatar
Ivan Tashkinov committed
174
    # An extra safety measure before we redirect (also done in `do_create_authorization/2`)
175
176
    if redirect_uri in String.split(app.redirect_uris) do
      redirect_uri = redirect_uri(conn, redirect_uri)
177
      url_params = %{code: auth.token}
178
      url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"])
179
      url = UriHelper.modify_uri_params(redirect_uri, url_params)
180
      redirect(conn, external: url)
181
182
    else
      conn
183
      |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
184
      |> redirect(external: redirect_uri(conn, redirect_uri))
185
186
187
    end
  end

188
  defp handle_create_authorization_error(
189
         %Plug.Conn{} = conn,
190
         {:error, scopes_issue},
191
192
         %{"authorization" => _} = params
       )
193
194
195
196
       when scopes_issue in [:unsupported_scopes, :missing_scopes] do
    # Per https://github.com/tootsuite/mastodon/blob/
    #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
    conn
197
    |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
198
    |> put_status(:unauthorized)
199
    |> authorize(params)
200
201
  end

202
  defp handle_create_authorization_error(
203
         %Plug.Conn{} = conn,
204
         {:account_status, :confirmation_pending},
205
206
         %{"authorization" => _} = params
       ) do
207
    conn
208
    |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
209
    |> put_status(:forbidden)
210
    |> authorize(params)
211
212
  end

213
214
215
216
217
  defp handle_create_authorization_error(
         %Plug.Conn{} = conn,
         {:mfa_required, user, auth, _},
         params
       ) do
218
    {:ok, token} = MFA.Token.create(user, auth)
219
220
221
222
223
224
225
226
227
228

    data = %{
      "mfa_token" => token.token,
      "redirect_uri" => params["authorization"]["redirect_uri"],
      "state" => params["authorization"]["state"]
    }

    MFAController.show(conn, data)
  end

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
  defp handle_create_authorization_error(
         %Plug.Conn{} = conn,
         {:account_status, :password_reset_pending},
         %{"authorization" => _} = params
       ) do
    conn
    |> put_flash(:error, dgettext("errors", "Password reset is required"))
    |> put_status(:forbidden)
    |> authorize(params)
  end

  defp handle_create_authorization_error(
         %Plug.Conn{} = conn,
         {:account_status, :deactivated},
         %{"authorization" => _} = params
       ) do
    conn
    |> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
    |> put_status(:forbidden)
    |> authorize(params)
  end

251
  defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
252
253
254
    Authenticator.handle_error(conn, error)
  end

Maksim's avatar
Maksim committed
255
256
  @doc "Renew access_token with refresh_token"
  def token_exchange(
257
        %Plug.Conn{} = conn,
Maksim's avatar
Maksim committed
258
        %{"grant_type" => "refresh_token", "refresh_token" => token} = _params
Maksim's avatar
Maksim committed
259
      ) do
Maksim's avatar
Maksim committed
260
    with {:ok, app} <- Token.Utils.fetch_app(conn),
Maksim's avatar
Maksim committed
261
262
         {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
         {:ok, token} <- RefreshToken.grant(token) do
263
      after_token_exchange(conn, %{user: user, token: token})
Maksim's avatar
Maksim committed
264
    else
265
      _error -> render_invalid_credentials_error(conn)
Maksim's avatar
Maksim committed
266
267
268
    end
  end

269
  def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do
Maksim's avatar
Maksim committed
270
    with {:ok, app} <- Token.Utils.fetch_app(conn),
Maksim's avatar
Maksim committed
271
272
         fixed_token = Token.Utils.fix_padding(params["code"]),
         {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
minibikini's avatar
minibikini committed
273
         %User{} = user <- User.get_cached_by_id(auth.user_id),
Maksim's avatar
Maksim committed
274
         {:ok, token} <- Token.exchange_token(app, auth) do
275
      after_token_exchange(conn, %{user: user, token: token})
eal's avatar
eal committed
276
    else
277
278
      error ->
        handle_token_exchange_error(conn, error)
279
280
    end
  end
eal's avatar
eal committed
281

lain's avatar
lain committed
282
  def token_exchange(
283
        %Plug.Conn{} = conn,
284
        %{"grant_type" => "password"} = params
lain's avatar
lain committed
285
      ) do
Maksim's avatar
Maksim committed
286
287
    with {:ok, %User{} = user} <- Authenticator.get_user(conn),
         {:ok, app} <- Token.Utils.fetch_app(conn),
288
289
         requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
         {:ok, token} <- login(user, app, requested_scopes) do
290
      after_token_exchange(conn, %{user: user, token: token})
291
    else
292
293
      error ->
        handle_token_exchange_error(conn, error)
294
295
296
    end
  end

297
  def token_exchange(
298
        %Plug.Conn{} = conn,
Maksim's avatar
Maksim committed
299
        %{"grant_type" => "password", "name" => name, "password" => _password} = params
300
301
302
303
304
305
306
307
308
      ) do
    params =
      params
      |> Map.delete("name")
      |> Map.put("username", name)

    token_exchange(conn, params)
  end

309
  def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do
Maksim's avatar
Maksim committed
310
    with {:ok, app} <- Token.Utils.fetch_app(conn),
311
         {:ok, auth} <- Authorization.create_authorization(app, %User{}),
Maksim's avatar
Maksim committed
312
         {:ok, token} <- Token.exchange_token(app, auth) do
313
      after_token_exchange(conn, %{token: token})
314
    else
315
316
      _error ->
        handle_token_exchange_error(conn, :invalid_credentails)
317
318
319
    end
  end

Maksim's avatar
Maksim committed
320
  # Bad request
321
  def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
Maksim's avatar
Maksim committed
322

323
324
325
326
327
328
  def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
    conn
    |> AuthHelper.put_session_token(token.token)
    |> json(OAuthView.render("token.json", view_params))
  end

329
330
331
332
333
334
  defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do
    conn
    |> put_status(:forbidden)
    |> json(build_and_response_mfa_token(user, auth))
  end

335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
  defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
    render_error(
      conn,
      :forbidden,
      "Your account is currently disabled",
      %{},
      "account_is_disabled"
    )
  end

  defp handle_token_exchange_error(
         %Plug.Conn{} = conn,
         {:account_status, :password_reset_pending}
       ) do
    render_error(
      conn,
      :forbidden,
      "Password reset is required",
      %{},
      "password_reset_required"
    )
  end

  defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
    render_error(
      conn,
      :forbidden,
      "Your login is missing a confirmed e-mail address",
      %{},
      "missing_confirmed_email"
    )
  end

368
369
370
371
372
373
374
375
376
377
  defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do
    render_error(
      conn,
      :forbidden,
      "Your account is awaiting approval.",
      %{},
      "awaiting_approval"
    )
  end

378
379
380
381
  defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
    render_invalid_credentials_error(conn)
  end

382
383
384
  def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do
    with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token),
         {:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do
385
      conn =
386
        with session_token = AuthHelper.get_session_token(conn),
387
             %Token{token: ^session_token} <- oauth_token do
388
          AuthHelper.delete_session_token(conn)
389
390
391
392
        else
          _ -> conn
        end

393
394
395
396
397
398
399
400
      json(conn, %{})
    else
      _error ->
        # RFC 7009: invalid tokens [in the request] do not cause an error response
        json(conn, %{})
    end
  end

401
  def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
Maksim's avatar
Maksim committed
402
403

  # Response for bad request
404
  defp bad_request(%Plug.Conn{} = conn, _) do
405
    render_error(conn, :internal_server_error, "Bad request")
Maksim's avatar
Maksim committed
406
407
  end

408
  @doc "Prepares OAuth request to provider for Ueberauth"
409
410
411
412
  def prepare_request(%Plug.Conn{} = conn, %{
        "provider" => provider,
        "authorization" => auth_attrs
      }) do
413
    scope =
414
415
416
      auth_attrs
      |> Scopes.fetch_scopes([])
      |> Scopes.to_string()
417
418

    state =
419
      auth_attrs
420
421
      |> Map.delete("scopes")
      |> Map.put("scope", scope)
feld's avatar
feld committed
422
      |> Jason.encode!()
423
424

    params =
425
      auth_attrs
426
427
428
      |> Map.drop(~w(scope scopes client_id redirect_uri))
      |> Map.put("state", state)

429
    # Handing the request to Ueberauth
430
    redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params))
431
432
  end

433
  def request(%Plug.Conn{} = conn, params) do
434
435
    message =
      if params["provider"] do
436
437
438
        dgettext("errors", "Unsupported OAuth provider: %{provider}.",
          provider: params["provider"]
        )
439
      else
440
        dgettext("errors", "Bad OAuth request.")
441
442
443
444
445
446
447
      end

    conn
    |> put_flash(:error, message)
    |> redirect(to: "/")
  end

448
  def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
449
    params = callback_params(params)
450
451
452
453
    messages = for e <- Map.get(failure, :errors, []), do: e.message
    message = Enum.join(messages, "; ")

    conn
454
455
456
457
    |> put_flash(
      :error,
      dgettext("errors", "Failed to authenticate: %{message}.", message: message)
    )
458
    |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
459
460
  end

461
  def callback(%Plug.Conn{} = conn, params) do
462
463
    params = callback_params(params)

464
465
    with {:ok, registration} <- Authenticator.get_registration(conn) do
      auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
466

Maksim's avatar
Maksim committed
467
468
469
      case Repo.get_assoc(registration, :user) do
        {:ok, user} ->
          create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
470

Maksim's avatar
Maksim committed
471
472
473
474
475
476
477
478
        _ ->
          registration_params =
            Map.merge(auth_attrs, %{
              "nickname" => Registration.nickname(registration),
              "email" => Registration.email(registration)
            })

          conn
479
          |> put_session_registration_id(registration.id)
Maksim's avatar
Maksim committed
480
          |> registration_details(%{"authorization" => registration_params})
481
482
      end
    else
483
484
485
      error ->
        Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))

486
        conn
487
        |> put_flash(:error, dgettext("errors", "Failed to set up user account."))
488
        |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
489
490
491
    end
  end

492
  defp callback_params(%{"state" => state} = params) do
feld's avatar
feld committed
493
    Map.merge(params, Jason.decode!(state))
494
495
  end

496
  def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
497
    render(conn, "register.html", %{
498
499
500
      client_id: auth_attrs["client_id"],
      redirect_uri: auth_attrs["redirect_uri"],
      state: auth_attrs["state"],
501
      scopes: Scopes.fetch_scopes(auth_attrs, []),
502
503
      nickname: auth_attrs["nickname"],
      email: auth_attrs["email"]
504
505
506
    })
  end

507
  def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
508
509
    with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
         %Registration{} = registration <- Repo.get(Registration, registration_id),
510
511
         {_, {:ok, auth, _user}} <-
           {:create_authorization, do_create_authorization(conn, params)},
512
513
514
515
         %User{} = user <- Repo.preload(auth, :user).user,
         {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
      conn
      |> put_session_registration_id(nil)
516
      |> after_create_authorization(auth, params)
517
    else
518
      {:create_authorization, error} ->
519
        {:register, handle_create_authorization_error(conn, error, params)}
520

521
522
      _ ->
        {:register, :generic_error}
523
524
525
    end
  end

526
  def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do
527
528
    with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
         %Registration{} = registration <- Repo.get(Registration, registration_id),
529
         {:ok, user} <- Authenticator.create_from_registration(conn, registration) do
530
531
532
      conn
      |> put_session_registration_id(nil)
      |> create_authorization(
533
        params,
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
        user: user
      )
    else
      {:error, changeset} ->
        message =
          Enum.map(changeset.errors, fn {field, {error, _}} ->
            "#{field} #{error}"
          end)
          |> Enum.join("; ")

        message =
          String.replace(
            message,
            "ap_id has already been taken",
            "nickname has already been taken"
          )

        conn
552
        |> put_status(:forbidden)
553
        |> put_flash(:error, "Error: #{message}.")
554
        |> registration_details(params)
555
556

      _ ->
557
        {:register, :generic_error}
558
559
560
    end
  end

561
562
  defp do_create_authorization(conn, auth_attrs, user \\ nil)

563
  defp do_create_authorization(
564
         %Plug.Conn{} = conn,
565
566
567
568
569
         %{
           "authorization" =>
             %{
               "client_id" => client_id,
               "redirect_uri" => redirect_uri
570
571
             } = auth_attrs
         },
572
         user
573
574
       ) do
    with {_, {:ok, %User{} = user}} <-
575
           {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
576
577
         %App{} = app <- Repo.get_by(App, client_id: client_id),
         true <- redirect_uri in String.split(app.redirect_uris),
578
579
         requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
         {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
580
      {:ok, auth, user}
581
582
583
    end
  end

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
  defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
       when is_list(requested_scopes) do
    with {:account_status, :active} <- {:account_status, User.account_status(user)},
         {:ok, scopes} <- validate_scopes(app, requested_scopes),
         {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
      {:ok, auth}
    end
  end

  # Note: intended to be a private function but opened for AccountController that logs in on signup
  @doc "If checks pass, creates authorization and token for given user, app and requested scopes."
  def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
    with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
         {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
         {:ok, token} <- Token.exchange_token(app, auth) do
      {:ok, token}
    end
  end

603
  # Special case: Local MastodonFE
604
  defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
605

606
  defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
607

608
  defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
609

610
  defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
611
    do: put_session(conn, :registration_id, registration_id)
Maksim's avatar
Maksim committed
612

613
  defp build_and_response_mfa_token(user, auth) do
614
    with {:ok, token} <- MFA.Token.create(user, auth) do
615
      MFAView.render("mfa_response.json", %{token: token, user: user})
616
617
618
    end
  end

619
  @spec validate_scopes(App.t(), map() | list()) ::
620
          {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
621
622
623
624
625
626
627
  defp validate_scopes(%App{} = app, params) when is_map(params) do
    requested_scopes = Scopes.fetch_scopes(params, app.scopes)
    validate_scopes(app, requested_scopes)
  end

  defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do
    Scopes.validate(requested_scopes, app.scopes)
628
  end
629

630
  def default_redirect_uri(%App{} = app) do
631
632
633
634
    app.redirect_uris
    |> String.split()
    |> Enum.at(0)
  end
635
636
637
638

  defp render_invalid_credentials_error(conn) do
    render_error(conn, :bad_request, "Invalid credentials")
  end
639
end