OAuth form user remembering feature. Local MastoFE login / logout fixes.
authorIvan Tashkinov <ivantashkinov@gmail.com>
Sat, 28 Nov 2020 18:51:06 +0000 (21:51 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Sat, 28 Nov 2020 18:51:06 +0000 (21:51 +0300)
14 files changed:
.gitattributes
CHANGELOG.md
docs/configuration/static_dir.md
lib/pleroma/user.ex
lib/pleroma/web/masto_fe_controller.ex
lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
lib/pleroma/web/o_auth/o_auth_controller.ex
lib/pleroma/web/templates/layout/app.html.eex
lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
priv/static/instance/static.css [new file with mode: 0644]
test/pleroma/user_test.exs
test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs
test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
test/pleroma/web/o_auth/o_auth_controller_test.exs

index 68895bf883004acdbc4f8faa03ae727cd1cb45ae..355e17f3cbcd8fdccb1968d863bbaa041e1f50d8 100644 (file)
@@ -1,8 +1,9 @@
 *.ex diff=elixir
 *.exs diff=elixir
-# At the time of writing all js/css files included
-# in the repo are minified bundles, and we don't want
-# to search/diff those as text files.
+
+# Most os js/css files included in the repo are minified bundles,
+#   and we don't want to search/diff those as text files. Exceptions are listed below.
 *.js binary
 *.js.map binary
 *.css binary
+priv/static/instance/static.css diff=css
index f4ef66408574649af2c092a8d0662912f27afc46..4b3ae2193281b836fd0b86e476615d40ee40e836 100644 (file)
@@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
 - The site title is now injected as a `title` tag like preloads or metadata.
 - Password reset tokens now are not accepted after a certain age.
+- OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved.
 
 <details>
   <summary>API Changes</summary>
index 8ac07b725f6bc34799981cea904af68780984e5c..a294bb604b0ed0e46d22998da8ec54d6fe966634 100644 (file)
@@ -88,3 +88,8 @@ config :pleroma, :frontend_configurations,
     Note the extra `static` folder for the terms-of-service.html
 
 Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by adding and changing `$static_dir/static/terms-of-service.html`.
+
+       
+## Styling rendered pages
+
+To overwrite the CSS stylesheet of the OAuth form and other static pages, you can upload your own CSS file to `instance/static/static.css`. This will completely replace the CSS used by those pages, so it might be a good idea to copy the one from `priv/static/instance/static.css` and make your changes.
index bcd5256c8e0d51203e6a00aab2ee974e2c6d1da3..6a5a43a2581a85164de214240492554ea20e77b2 100644 (file)
@@ -2406,4 +2406,8 @@ defmodule Pleroma.User do
     |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
     |> Map.put(:fields, fields)
   end
+
+  def get_host(%User{ap_id: ap_id} = _user) do
+    URI.parse(ap_id).host
+  end
 end
index 08f92d55fb097f2fcf9a78424a360b1cd1cdfd15..7011ae214ab6bd15cfcc1426ab01ca42f1ecd5d8 100644 (file)
@@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastoFEController do
   use Pleroma.Web, :controller
 
   alias Pleroma.User
+  alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.MastodonAPI.AuthController
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
@@ -26,27 +28,27 @@ defmodule Pleroma.Web.MastoFEController do
   )
 
   @doc "GET /web/*path"
-  def index(%{assigns: %{user: user, token: token}} = conn, _params)
-      when not is_nil(user) and not is_nil(token) do
-    conn
-    |> put_layout(false)
-    |> render("index.html",
-      token: token.token,
-      user: user,
-      custom_emojis: Pleroma.Emoji.get_all()
-    )
-  end
-
   def index(conn, _params) do
-    conn
-    |> put_session(:return_to, conn.request_path)
-    |> redirect(to: "/web/login")
+    with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
+         {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
+      conn
+      |> put_layout(false)
+      |> render("index.html",
+        token: token.token,
+        user: user,
+        custom_emojis: Pleroma.Emoji.get_all()
+      )
+    else
+      _ ->
+        conn
+        |> put_session(:return_to, conn.request_path)
+        |> redirect(to: "/web/login")
+    end
   end
 
   @doc "GET /web/manifest.json"
   def manifest(conn, _params) do
-    conn
-    |> render("manifest.json")
+    render(conn, "manifest.json")
   end
 
   @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
index fa582dcfca16abff899634b07ee6790e1ae9006b..93d057a79ef6bbe35877572c6f2cd990e6bca901 100644 (file)
@@ -8,10 +8,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
   alias Pleroma.Helpers.AuthHelper
+  alias Pleroma.Helpers.UriHelper
   alias Pleroma.User
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
   alias Pleroma.Web.TwitterAPI.TwitterAPI
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -21,24 +23,35 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
   @local_mastodon_name "Mastodon-Local"
 
   @doc "GET /web/login"
-  def login(%{assigns: %{user: %User{}}} = conn, _params) do
-    redirect(conn, to: local_mastodon_root_path(conn))
-  end
-
-  # Local Mastodon FE login init action
-  def login(conn, %{"code" => auth_token}) do
-    with {:ok, app} <- get_or_make_app(),
+  # Local Mastodon FE login callback action
+  def login(conn, %{"code" => auth_token} = params) do
+    with {:ok, app} <- local_mastofe_app(),
          {:ok, auth} <- Authorization.get_by_token(app, auth_token),
-         {:ok, token} <- Token.exchange_token(app, auth) do
+         {:ok, oauth_token} <- Token.exchange_token(app, auth) do
+      redirect_to =
+        conn
+        |> local_mastodon_post_login_path()
+        |> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token})
+
       conn
-      |> AuthHelper.put_session_token(token.token)
-      |> redirect(to: local_mastodon_root_path(conn))
+      |> AuthHelper.put_session_token(oauth_token.token)
+      |> redirect(to: redirect_to)
+    else
+      _ -> redirect_to_oauth_form(conn, params)
+    end
+  end
+
+  def login(conn, params) do
+    with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn,
+         {:ok, %{id: ^app_id}} <- local_mastofe_app() do
+      redirect(conn, to: local_mastodon_post_login_path(conn))
+    else
+      _ -> redirect_to_oauth_form(conn, params)
     end
   end
 
-  # Local Mastodon FE callback action
-  def login(conn, _) do
-    with {:ok, app} <- get_or_make_app() do
+  defp redirect_to_oauth_form(conn, _params) do
+    with {:ok, app} <- local_mastofe_app() do
       path =
         o_auth_path(conn, :authorize,
           response_type: "code",
@@ -53,9 +66,16 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
 
   @doc "DELETE /auth/sign_out"
   def logout(conn, _) do
-    conn
-    |> clear_session()
-    |> redirect(to: "/")
+    conn =
+      with %{assigns: %{token: %Token{} = oauth_token}} <- conn,
+           session_token = AuthHelper.get_session_token(conn),
+           {:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do
+        AuthHelper.delete_session_token(conn)
+      else
+        _ -> conn
+      end
+
+    redirect(conn, to: "/")
   end
 
   @doc "POST /auth/password"
@@ -67,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
     json_response(conn, :no_content, "")
   end
 
-  defp local_mastodon_root_path(conn) do
+  defp local_mastodon_post_login_path(conn) do
     case get_session(conn, :return_to) do
       nil ->
         masto_fe_path(conn, :index, ["getting-started"])
@@ -78,9 +98,11 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
     end
   end
 
-  @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
-  defp get_or_make_app do
-    %{client_name: @local_mastodon_name, redirect_uris: "."}
-    |> App.get_or_make(["read", "write", "follow", "push", "admin"])
+  @spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  def local_mastofe_app do
+    App.get_or_make(
+      %{client_name: @local_mastodon_name, redirect_uris: "."},
+      ["read", "write", "follow", "push", "admin"]
+    )
   end
 end
index 8103395b3c04db0ce59ceadf4d3c517f67131e5e..965c0f87933b8f3b21aa2aa92edcb2c2b22dbb3b 100644 (file)
@@ -80,6 +80,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     available_scopes = (app && app.scopes) || []
     scopes = Scopes.fetch_scopes(params, available_scopes)
 
+    user =
+      with %{assigns: %{user: %User{} = user}} <- conn do
+        user
+      else
+        _ -> nil
+      end
+
     scopes =
       if scopes == [] do
         available_scopes
@@ -89,6 +96,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 
     # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
     render(conn, Authenticator.auth_template(), %{
+      user: user,
+      app: app && Map.delete(app, :client_secret),
       response_type: params["response_type"],
       client_id: params["client_id"],
       available_scopes: available_scopes,
@@ -132,11 +141,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     end
   end
 
-  def create_authorization(
-        %Plug.Conn{} = conn,
-        %{"authorization" => _} = params,
-        opts \\ []
-      ) do
+  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
     with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
          {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
       after_create_authorization(conn, auth, params)
index 3f28f1920a1816d760ec65e9173f559f084392e1..1ede59fd850cef070e7ee4f41015426ae9fc477a 100644 (file)
 <!DOCTYPE html>
-<html>
+<html lang="en">
   <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
-    <title>
-    <%= Pleroma.Config.get([:instance, :name]) %>
-    </title>
-    <style>
-      body {
-        background-color: #121a24;
-        font-family: sans-serif;
-        color: #b9b9ba;
-        text-align: center;
-      }
-
-      .container {
-        max-width: 420px;
-        padding: 20px;
-        background-color: #182230;
-        border-radius: 4px;
-        margin: auto;
-        margin-top: 10vh;
-        box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
-      }
-
-      h1 {
-        margin: 0;
-        font-size: 24px;
-      }
-
-      h2 {
-        color: #b9b9ba;
-        font-weight: normal;
-        font-size: 18px;
-        margin-bottom: 20px;
-      }
-
-      a {
-        color: #d8a070;
-        text-decoration: none;
-      }
-
-      form {
-        width: 100%;
-      }
-
-      .input {
-        text-align: left;
-        color: #89898a;
-        display: flex;
-        flex-direction: column;
-      }
-
-      input {
-        box-sizing: content-box;
-        padding: 10px;
-        margin-top: 5px;
-        margin-bottom: 10px;
-        background-color: #121a24;
-        color: #b9b9ba;
-        border: 0;
-        transition-property: border-bottom;
-        transition-duration: 0.35s;
-        border-bottom: 2px solid #2a384a;
-        font-size: 14px;
-      }
-
-      .scopes-input {
-        display: flex;
-        flex-direction: column;
-        margin-top: 1em;
-        text-align: left;
-        color: #89898a;
-      }
-
-      .scopes-input label:first-child {
-        height: 2em;
-      }
-
-      .scopes {
-        display: flex;
-        flex-wrap: wrap;
-        text-align: left;
-        color: #b9b9ba;
-      }
-
-      .scope {
-        display: flex;
-        flex-basis: 100%;
-        height: 2em;
-        align-items: center;
-      }
-
-      .scope:before {
-        color: #b9b9ba;
-        content: "✔\fe0e";
-        margin-left: 1em;
-        margin-right: 1em;
-      }
-
-      [type="checkbox"] + label {
-        display: none;
-        cursor: pointer;
-        margin: 0.5em;
-      }
-
-      [type="checkbox"] {
-        display: none;
-      }
-
-      [type="checkbox"] + label:before {
-        cursor: pointer;
-        display: inline-block;
-        color: white;
-        background-color: #121a24;
-        border: 4px solid #121a24;
-        box-shadow: 0px 0px 1px 0 #d8a070;
-        box-sizing: border-box;
-        width: 1.2em;
-        height: 1.2em;
-        margin-right: 1.0em;
-        content: "";
-        transition-property: background-color;
-        transition-duration: 0.35s;
-        color: #121a24;
-        margin-bottom: -0.2em;
-        border-radius: 2px;
-      }
-
-      [type="checkbox"]:checked + label:before {
-        background-color: #d8a070;
-      }
-
-      input:focus {
-        outline: none;
-        border-bottom: 2px solid #d8a070;
-      }
-
-      button {
-        box-sizing: border-box;
-        width: 100%;
-        background-color: #1c2a3a;
-        color: #b9b9ba;
-        border-radius: 4px;
-        border: none;
-        padding: 10px;
-        margin-top: 20px;
-        margin-bottom: 20px;
-        text-transform: uppercase;
-        font-size: 16px;
-        box-shadow: 0px 0px 2px 0px black,
-          0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
-          0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
-      }
-
-      button:hover {
-        cursor: pointer;
-        box-shadow: 0px 0px 0px 1px #d8a070,
-          0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
-          0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
-      }
-
-      .alert-danger {
-        box-sizing: border-box;
-        width: 100%;
-        background-color: #931014;
-        border: 1px solid #a06060;
-        border-radius: 4px;
-        padding: 10px;
-        margin-top: 20px;
-        font-weight: 500;
-        font-size: 16px;
-      }
-
-      .alert-info {
-        box-sizing: border-box;
-        width: 100%;
-        border-radius: 4px;
-        border: 1px solid #7d796a;
-        padding: 10px;
-        margin-top: 20px;
-        font-weight: 500;
-        font-size: 16px;
-      }
-
-      @media all and (max-width: 440px) {
-        .container {
-          margin-top: 0
-        }
-
-        .scope {
-          flex-basis: 0%;
-        }
-
-        .scope:before {
-          content: "";
-          margin-left: 0em;
-          margin-right: 1em;
-        }
-
-        .scope:first-child:before {
-          margin-left: 1em;
-          content: "✔\fe0e";
-        }
-
-        .scope:after {
-          content: ",";
-        }
-
-        .scope:last-child:after {
-          content: "";
-        }
-      }
-      .form-row {
-        display: flex;
-      }
-      .form-row > label {
-        text-align: left;
-        line-height: 47px;
-        flex: 1;
-      }
-      .form-row > input {
-        flex: 2;
-      }
-    </style>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
+    <title><%= Pleroma.Config.get([:instance, :name]) %></title>
+    <link rel="stylesheet" href="/instance/static.css">
   </head>
   <body>
+    <div class="instance-header">
+      <a class="instance-header__content" href="/">
+        <img class="instance-header__thumbnail" src="<%= Pleroma.Config.get([:instance, :instance_thumbnail]) %>">
+        <h1 class="instance-header__title"><%= Pleroma.Config.get([:instance, :name]) %></h1>
+      </a>
+    </div>
     <div class="container">
-      <h1><%= Pleroma.Config.get([:instance, :name]) %></h1>
       <%= @inner_content %>
     </div>
   </body>
index b17142ff8ffd066c8f6579d87de3616066fe1b3e..1a85818ecbad26e4f77371f86653b7b1a90291db 100644 (file)
@@ -5,32 +5,55 @@
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 <% end %>
 
-<h2>OAuth Authorization</h2>
 <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
 
-<%= if @params["registration"] in ["true", true] do %>
-  <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
-  <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
-  <div class="input">
-    <%= label f, :nickname, "Pleroma Handle" %>
-    <%= text_input f, :nickname, placeholder: "lain" %>
+<%= if @user do %>
+  <div class="account-header">
+    <div class="account-header__banner" style="background-image: url('<%= Pleroma.User.banner_url(@user) %>')"></div>
+    <div class="account-header__avatar" style="background-image: url('<%= Pleroma.User.avatar_url(@user) %>')"></div>
+    <div class="account-header__meta">
+      <div class="account-header__display-name"><%= @user.name %></div>
+      <div class="account-header__nickname">@<%= @user.nickname %>@<%= Pleroma.User.get_host(@user) %></div>
+    </div>
   </div>
-  <%= hidden_input f, :name, value: @params["name"] %>
-  <%= hidden_input f, :password, value: @params["password"] %>
-  <br>
-<% else %>
-  <div class="input">
-    <%= label f, :name, "Username" %>
-    <%= text_input f, :name %>
-  </div>
-  <div class="input">
-    <%= label f, :password, "Password" %>
-    <%= password_input f, :password %>
-  </div>
-  <%= submit "Log In" %>
-  <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
 <% end %>
 
+<div class="container__content">
+  <%= if @app do %>
+    <p>Application <strong><%= @app.client_name %></strong> is requesting access to your account.</p>
+    <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+  <% end %>
+
+  <%= if @user do %>
+    <div class="actions">
+      <a class="button button--cancel" href="/">Cancel</a>
+      <%= submit "Approve", class: "button--approve" %>
+    </div>
+  <% else %>
+    <%= if @params["registration"] in ["true", true] do %>
+      <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
+      <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
+      <div class="input">
+        <%= label f, :nickname, "Pleroma Handle" %>
+        <%= text_input f, :nickname, placeholder: "lain" %>
+      </div>
+      <%= hidden_input f, :name, value: @params["name"] %>
+      <%= hidden_input f, :password, value: @params["password"] %>
+      <br>
+    <% else %>
+      <div class="input">
+        <%= label f, :name, "Username" %>
+        <%= text_input f, :name %>
+      </div>
+      <div class="input">
+        <%= label f, :password, "Password" %>
+        <%= password_input f, :password %>
+      </div>
+      <%= submit "Log In" %>
+    <% end %>
+  <% end %>
+</div>
+
 <%= hidden_input f, :client_id, value: @client_id %>
 <%= hidden_input f, :response_type, value: @response_type %>
 <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
@@ -40,4 +63,3 @@
 <%= if Pleroma.Config.oauth_consumer_enabled?() do %>
   <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
 <% end %>
-
diff --git a/priv/static/instance/static.css b/priv/static/instance/static.css
new file mode 100644 (file)
index 0000000..487e1ec
--- /dev/null
@@ -0,0 +1,296 @@
+* {
+  box-sizing: border-box;
+}
+
+:root {
+  --brand-color: #d8a070;
+  --background-color: #121a24;
+  --foreground-color: #182230;
+  --primary-text-color: #b9b9ba;
+  --muted-text-color: #89898a;
+}
+
+body {
+  background-color: var(--background-color);
+  font-family: sans-serif;
+  color: var(--primary-text-color);
+  padding: 0;
+  margin: 0;
+}
+
+.instance-header {
+  height: 60px;
+  padding: 10px;
+  background: var(--foreground-color);
+  box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
+}
+
+.instance-header__content {
+  display: flex;
+  align-items: center;
+  max-width: 400px;
+  margin: 0 auto;
+}
+
+.instance-header__thumbnail {
+  max-width: 40px;
+  border-radius: 4px;
+  margin-right: 12px;
+}
+
+.instance-header__title {
+  font-size: 16px;
+  font-weight: bold;
+  color: var(--primary-text-color);
+}
+
+.container {
+  max-width: 400px;
+  background-color: var(--foreground-color);
+  border-radius: 4px;
+  overflow: hidden;
+  margin: 35px auto;
+  box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
+}
+
+.container__content {
+  padding: 0 20px;
+}
+
+h1 {
+  margin: 0;
+  font-size: 24px;
+  text-align: center;
+}
+
+h2 {
+  color: var(--primary-text-color);
+  font-weight: normal;
+  font-size: 18px;
+  margin-bottom: 20px;
+}
+
+a {
+  color: var(--brand-color);
+  text-decoration: none;
+}
+
+form {
+  width: 100%;
+}
+
+.input {
+  color: var(--muted-text-color);
+  display: flex;
+  flex-direction: column;
+}
+
+input {
+  box-sizing: content-box;
+  padding: 10px;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  background-color: var(--background-color);
+  color: var(--primary-text-color);
+  border: 0;
+  transition-property: border-bottom;
+  transition-duration: 0.35s;
+  border-bottom: 2px solid #2a384a;
+  font-size: 14px;
+}
+
+.scopes-input {
+  display: flex;
+  flex-direction: column;
+  margin: 1em 0;
+  color: var(--muted-text-color);
+}
+
+.scopes-input label:first-child {
+  height: 2em;
+}
+
+.scopes {
+  display: flex;
+  flex-wrap: wrap;
+  color: var(--primary-text-color);
+}
+
+.scope {
+  display: flex;
+  flex-basis: 100%;
+  height: 2em;
+  align-items: center;
+}
+
+.scope:before {
+  color: var(--primary-text-color);
+  content: "✔\fe0e";
+  margin-left: 1em;
+  margin-right: 1em;
+}
+
+[type="checkbox"] + label {
+  display: none;
+  cursor: pointer;
+  margin: 0.5em;
+}
+
+[type="checkbox"] {
+  display: none;
+}
+
+[type="checkbox"] + label:before {
+  cursor: pointer;
+  display: inline-block;
+  color: white;
+  background-color: var(--background-color);
+  border: 4px solid var(--background-color);
+  box-shadow: 0px 0px 1px 0 var(--brand-color);
+  width: 1.2em;
+  height: 1.2em;
+  margin-right: 1.0em;
+  content: "";
+  transition-property: background-color;
+  transition-duration: 0.35s;
+  color: var(--background-color);
+  margin-bottom: -0.2em;
+  border-radius: 2px;
+}
+
+[type="checkbox"]:checked + label:before {
+  background-color: var(--brand-color);
+}
+
+input:focus {
+  outline: none;
+  border-bottom: 2px solid var(--brand-color);
+}
+
+.actions {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.actions button,
+.actions a.button {
+  width: auto;
+  margin-left: 10px;
+}
+
+a.button,
+button {
+  width: 100%;
+  background-color: #1c2a3a;
+  color: var(--primary-text-color);
+  border-radius: 4px;
+  border: none;
+  padding: 10px 16px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  text-transform: uppercase;
+  font-size: 16px;
+  box-shadow: 0px 0px 2px 0px black,
+    0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
+    0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
+}
+
+a.button:hover,
+button:hover {
+  cursor: pointer;
+  box-shadow: 0px 0px 0px 1px var(--brand-color),
+    0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
+    0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
+}
+
+.alert-danger {
+  width: 100%;
+  background-color: #931014;
+  border: 1px solid #a06060;
+  border-radius: 4px;
+  padding: 10px;
+  margin-top: 20px;
+  font-weight: 500;
+  font-size: 16px;
+}
+
+.alert-info {
+  width: 100%;
+  border-radius: 4px;
+  border: 1px solid #7d796a;
+  padding: 10px;
+  margin-top: 20px;
+  font-weight: 500;
+  font-size: 16px;
+}
+
+.account-header__banner {
+  width: 100%;
+  height: 112px;
+  background-size: cover;
+  background-position: center;
+}
+
+.account-header__avatar {
+  width: 94px;
+  height: 94px;
+  background-size: cover;
+  background-position: center;
+  margin: -47px 10px 0;
+  border: 6px solid var(--foreground-color);
+  border-radius: 999px;
+}
+
+.account-header__meta {
+  padding: 6px 20px 17px;
+}
+
+.account-header__display-name {
+  font-size: 20px;
+  font-weight: bold;
+}
+
+.account-header__nickname {
+  font-size: 14px;
+  color: var(--muted-text-color);
+}
+
+@media all and (max-width: 420px) {
+  .container {
+    margin: 0 auto;
+    border-radius: 0;
+  }
+
+  .scope {
+    flex-basis: 0%;
+  }
+
+  .scope:before {
+    content: "";
+    margin-left: 0em;
+    margin-right: 1em;
+  }
+
+  .scope:first-child:before {
+    margin-left: 1em;
+    content: "✔\fe0e";
+  }
+
+  .scope:after {
+    content: ",";
+  }
+
+  .scope:last-child:after {
+    content: "";
+  }
+}
+.form-row {
+  display: flex;
+}
+.form-row > label {
+  line-height: 47px;
+  flex: 1;
+}
+.form-row > input {
+  flex: 2;
+}
index c678dadb3075ba0e092156beee902a4037702c66..1ba7f2a2f2a14fadf02d5c2208ad5935bbd3f515 100644 (file)
@@ -2171,4 +2171,9 @@ defmodule Pleroma.UserTest do
 
     assert User.avatar_url(user, no_default: true) == nil
   end
+
+  test "get_host/1" do
+    user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain")
+    assert User.get_host(user) == "lain.com"
+  end
 end
index bf2438fe2da61f5c9c34a035b71fb6a3222f2892..d7834c876d820b10092cf9c61a278242aee555e8 100644 (file)
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do
         |> get("/web/login", %{code: auth.token})
 
       assert conn.status == 302
-      assert redirected_to(conn) == path
+      assert redirected_to(conn) =~ path
     end
 
     test "redirects to the getting-started page when referer is not present", %{conn: conn} do
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do
       conn = get(conn, "/web/login", %{code: auth.token})
 
       assert conn.status == 302
-      assert redirected_to(conn) == "/web/getting-started"
+      assert redirected_to(conn) =~ "/web/getting-started"
     end
   end
 
index ed8add8d21077961bdcc3446f6a542f5680fe033..b9cd050df38b85ab8dcf0a3d59a94cf363f77e2f 100644 (file)
@@ -64,7 +64,8 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do
     end
 
     test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
-      token = insert(:oauth_token, scopes: ["read"])
+      {:ok, app} = Pleroma.Web.MastodonAPI.AuthController.local_mastofe_app()
+      token = insert(:oauth_token, app: app, scopes: ["read"])
 
       conn =
         conn
index 9c1debd06e7395cbb55ca6c65d602685d4bc2225..b7fe5785fa703d96aeaf002e237fabbb9894e910 100644 (file)
@@ -611,6 +611,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       end
     end
 
+    test "authorize from cookie" do
+      user = insert(:user)
+      app = insert(:oauth_app)
+      oauth_token = insert(:oauth_token, user: user, app: app)
+      redirect_uri = OAuthController.default_redirect_uri(app)
+
+      conn =
+        build_conn()
+        |> Plug.Session.call(Plug.Session.init(@session_opts))
+        |> fetch_session()
+        |> AuthHelper.put_session_token(oauth_token.token)
+        |> post(
+          "/oauth/authorize",
+          %{
+            "authorization" => %{
+              "name" => user.nickname,
+              "client_id" => app.client_id,
+              "redirect_uri" => redirect_uri,
+              "scope" => app.scopes,
+              "state" => "statepassed"
+            }
+          }
+        )
+
+      target = redirected_to(conn)
+      assert target =~ redirect_uri
+
+      query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
+
+      assert %{"state" => "statepassed", "code" => code} = query
+      auth = Repo.get_by(Authorization, token: code)
+      assert auth
+      assert auth.scopes == app.scopes
+    end
+
     test "redirect to on two-factor auth page" do
       otp_secret = TOTP.generate_secret()
 
@@ -1221,8 +1256,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     end
   end
 
-  describe "POST /oauth/revoke - bad request" do
-    test "returns 500" do
+  describe "POST /oauth/revoke" do
+    test "returns 500 on bad request" do
       response =
         build_conn()
         |> post("/oauth/revoke", %{})