OAuth form user remembering feature. Local MastoFE login / logout fixes.
This commit is contained in:
parent
62993db499
commit
f1b07a2b2b
14 changed files with 488 additions and 297 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
# Local Mastodon FE callback action
|
||||
def login(conn, _) do
|
||||
with {:ok, app} <- get_or_make_app() do
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,233 +1,19 @@
|
|||
<!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>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue