26 Commits

Author SHA1 Message Date
Râu Cao
0f3b9f176e 0.8.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-03 15:35:46 +02:00
822ae2f945 Merge pull request 'Fix migration failing with PostgreSQL' (#145) from bugfix/144-postgres_migration into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #145
2023-09-03 13:32:36 +00:00
Râu Cao
96c669ab4e Update database schema, fix spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2023-09-03 15:29:09 +02:00
Râu Cao
558100c35e Fix migration failing with PostgreSQL 2023-09-03 15:28:32 +02:00
Râu Cao
6739b38f4c 0.8.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-01 12:18:26 +02:00
7e1272c936 Merge pull request 'Service pages for Chat and Social' (#143) from feature/service_pages into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #143
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-01 08:36:09 +00:00
Râu Cao
ecdeb4c122 Fix copypasta
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-09-01 10:32:11 +02:00
Râu Cao
8614e2f12b Use service configs on dashboard
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Only show enabled services, and use the URLs from the various configs.
2023-08-13 17:24:10 +02:00
Râu Cao
a038a857d9 Make Drone CI configurable 2023-08-13 17:23:57 +02:00
Râu Cao
eee81d0cf1 Small link improvement
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-13 15:41:57 +02:00
Râu Cao
b7fa4b012a Allow Mastodon address domain to be different from primary domain
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-13 12:27:05 +02:00
Râu Cao
10bcd5c32b Ignore .env.development 2023-08-13 12:26:56 +02:00
Râu Cao
f79d5d4724 Use select element instead of tabs on mobile
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-11 14:17:12 +02:00
Râu Cao
866ffbe615 Upgrade tailwindcss-stimulus-components to latest version
All checks were successful
continuous-integration/drone/push Build is passing
The latest one offers more tabs features. Required some changes to the
modals and tabs code.
2023-08-11 13:58:57 +02:00
Râu Cao
3c1fe3396d Add Mastodon service page 2023-08-11 13:58:53 +02:00
Râu Cao
e4242333d9 Add recommended apps for Chat/XMPP
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 19:59:29 +02:00
Râu Cao
138f13c1a0 Add note
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-07 18:16:40 +02:00
Râu Cao
ad5e515200 Update README 2023-08-07 18:16:34 +02:00
Râu Cao
1ea8b22a59 WIP Add service page for Chat
Some checks are pending
continuous-integration/drone/push Build is running
2023-08-07 18:16:14 +02:00
Râu Cao
f49aff262c Add base controller for service controllers
Some checks are pending
continuous-integration/drone/push Build is running
2023-08-07 18:15:17 +02:00
852e2fea1e Merge pull request 'remoteStorage OAuth' (#109) from feature/rs-oauth into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #109
2023-08-04 08:55:28 +00:00
Râu Cao
353b55fe1a Add RS OAuth controller specs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-08-01 14:29:24 +02:00
Râu Cao
ba0cbba96b Add feature spec for RS OAuth dialog
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-01 13:01:41 +02:00
Râu Cao
5f921f1b53 RS OAuth pre-fills username for login 2023-08-01 13:01:03 +02:00
Râu Cao
a2d27bf575 Support pre-filling of username in login form 2023-08-01 13:00:22 +02:00
Râu Cao
fcf9a065e1 Fix specs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-14 15:56:28 +02:00
54 changed files with 2675 additions and 133 deletions

View File

@@ -22,6 +22,8 @@ WEBHOOKS_ALLOWED_IPS='10.1.1.163'
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
DRONECI_PUBLIC_URL='https://drone.kosmos.org'
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
MASTODON_PUBLIC_URL='https://kosmos.social'
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'

1
.gitignore vendored
View File

@@ -39,6 +39,7 @@ yarn-debug.log*
# Ignore local dotenv config file
.env
.env.development
# Ignore redis dumps from sidekiq
dump.rdb

View File

@@ -64,6 +64,7 @@ group :development, :test do
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
gem 'rspec-rails'
gem 'rails-controller-testing'
gem "byebug", "~> 11.1"
end

View File

@@ -269,6 +269,10 @@ GEM
activesupport (= 7.0.5)
bundler (>= 1.15.0)
railties (= 7.0.5)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
@@ -444,6 +448,7 @@ DEPENDENCIES
pg (~> 1.2.3)
puma (~> 4.1)
rails (~> 7.0.2)
rails-controller-testing
rails-settings-cached (~> 2.8.3)
rqrcode (~> 2.0)
rspec-rails

View File

@@ -107,6 +107,7 @@ command:
* [Tailwind CSS](https://tailwindcss.com/)
* [Sass](https://sass-lang.com/documentation)
* [Stimulus](https://stimulus.hotwired.dev/handbook/)
* [Tailwind Stimulus Components](https://github.com/excid3/tailwindcss-stimulus-components)
### Testing

View File

@@ -0,0 +1,15 @@
<div class="flex">
<div class="<%= @icon_container_class %>">
<%= image_tag(@icon_path, class: 'h-full w-full') %>
</div>
<div class="flex-1 px-4">
<h4 class="sm:pt-2 mb-2 text-lg font-bold"><%= @name %></h4>
<p class="leading-snug"><%= @description %></p>
<p class="leading-snug flex flex-wrap gap-3">
<% @links.each do |link| %>
<a href="<%= link[1] %>" target="_blank"
class="flex-0 btn-sm btn-gray"><%= link[0] %></a>
<% end %>
</p>
</div>
</div>

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
class AppInfoComponent < ViewComponent::Base
def initialize(name:, description:, icon_path: , icon_fill_box: false, links: [])
@name = name
@description = description
@icon_path = icon_path
@icon_container_class = icon_container_class(icon_fill_box)
@links = links
end
def icon_container_class(icon_fill_box)
str = "flex-0 h-16 w-16 sm:h-28 sm:w-28 bg-white rounded-3xl overflow-hidden"
unless icon_fill_box
str += " p-2 border border-gray-200"
end
str
end
end

View File

@@ -1,13 +1,26 @@
<div data-modal-target="container"
data-action="click->modal#closeBackground keyup@window->modal#closeWithKeyboard"
class="hidden animate-scale-in fixed inset-0 overflow-y-auto flex items-center justify-center"
style="z-index: 9999;">
<div class="max-h-screen w-auto max-w-lg relative">
<div class="m-1 bg-white rounded shadow">
<div class="p-8">
<%= content %>
<div class="flex justify-end items-center flex-wrap mt-6">
<button class="btn-md btn-blue" data-action="click->modal#close">Close</button>
<div tabindex="-1" class="relative z-10">
<!-- Modal Background -->
<div class="hidden fixed inset-0 bg-black bg-opacity-80 overflow-y-auto flex items-center justify-center"
data-modal-target="background"
data-action="click->modal#closeBackground"
data-transition-enter="transition-all ease-in-out duration-100"
data-transition-enter-from="bg-opacity-0"
data-transition-enter-to="bg-opacity-80"
data-transition-leave="transition-all ease-in-out duration-100"
data-transition-leave-from="bg-opacity-80"
data-transition-leave-to="bg-opacity-0">
<!-- Modal Container -->
<div data-modal-target="container"
class="max-h-screen w-auto max-w-lg relative
hidden animate-scale-in fixed inset-0 overflow-y-auto flex items-center justify-center">
<!-- Modal Card -->
<div class="m-1 bg-white rounded shadow">
<div class="p-8">
<%= content %>
<div class="flex justify-end items-center flex-wrap mt-6">
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
class Rs::OauthController < ApplicationController
before_action :require_user_signed_in
before_action :require_signed_in_with_username, only: :new
before_action :authenticate_user!, only: :create
def new
username, org = params[:useraddress].split("@")
@@ -30,23 +31,26 @@ class Rs::OauthController < ApplicationController
end
unless @client_id.present?
redirect_to url_with_state("#{@redirect_uri}#error=invalid_request", @state) and return
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_request", @state),
allow_other_host: true) and return
end
if @scopes.empty?
redirect_to url_with_state("#{@redirect_uri}#error=invalid_scope", @state) and return
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_scope", @state),
allow_other_host: true) and return
end
unless hostname_of(@client_id) == hostname_of(@redirect_uri)
redirect_to url_with_state("#{@redirect_uri}#error=invalid_client", @state) and return
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_client", @state),
allow_other_host: true) and return
end
@client_id.gsub!(/http(s)?:\/\//, "")
# TODO
# if auth = current_user.remote_storage_authorizations.valid.where(permissions: @scopes, client_id: @client_id).first
# redirect_to url_with_state("#{@redirect_uri}#access_token=#{auth.token}", @state), allow_other_host: true
# end
if auth = current_user.remote_storage_authorizations.valid.where(permissions: @scopes, client_id: @client_id).first
redirect_to(url_with_state("#{@redirect_uri}#access_token=#{auth.token}", @state),
allow_other_host: true) and return
end
end
def create
@@ -64,15 +68,18 @@ class Rs::OauthController < ApplicationController
http_status :bad_request and return unless redirect_uri.present?
if permissions.empty?
redirect_to url_with_state("#{redirect_uri}#error=invalid_scope", state), allow_other_host: true and return
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
allow_other_host: true) and return
end
unless client_id.present?
redirect_to url_with_state("#{redirect_uri}#error=invalid_request", state), allow_other_host: true and return
redirect_to(url_with_state("#{redirect_uri}#error=invalid_request", state),
allow_other_host: true) and return
end
unless hostname_of(client_id) == hostname_of(redirect_uri)
redirect_to url_with_state("#{redirect_uri}#error=invalid_client", state), allow_other_host: true and return
redirect_to(url_with_state("#{redirect_uri}#error=invalid_client", state),
allow_other_host: true) and return
end
client_id.gsub!(/http(s)?:\/\//, "")
@@ -85,18 +92,26 @@ class Rs::OauthController < ApplicationController
expire_at: expire_at
)
redirect_to url_with_state("#{redirect_uri}#access_token=#{auth.token}", state), allow_other_host: true
redirect_to url_with_state("#{redirect_uri}#access_token=#{auth.token}", state),
allow_other_host: true
end
# GET /rs/oauth/token/:id/launch_app
def launch_app
auth = current_user.remote_storage_authorizations.find(params[:id])
redirect_to app_auth_url(auth)
redirect_to app_auth_url(auth), allow_other_host: true
end
private
def require_signed_in_with_username
unless user_signed_in?
username, org = params[:useraddress].split("@")
redirect_to new_user_session_path(cn: username, ou: org)
end
end
def app_auth_url(auth)
url = "#{auth.url}#remotestorage=#{current_user.address}"
url += "&access_token=#{auth.token}"

View File

@@ -0,0 +1,9 @@
class Services::BaseController < ApplicationController
before_action :set_current_section
private
def set_current_section
@current_section = :services
end
end

View File

@@ -0,0 +1,14 @@
class Services::ChatController < Services::BaseController
before_action :authenticate_user!
before_action :require_service_available
def show
@service_enabled = current_user.services_enabled.include?(:xmpp)
end
private
def require_service_available
http_status :not_found unless Setting.ejabberd_enabled?
end
end

View File

@@ -0,0 +1,14 @@
class Services::MastodonController < Services::BaseController
before_action :authenticate_user!
before_action :require_service_available
def show
@service_enabled = current_user.services_enabled.include?(:mastodon)
end
private
def require_service_available
http_status :not_found unless Setting.mastodon_enabled?
end
end

View File

@@ -1,8 +1,7 @@
class Services::RemotestorageController < ApplicationController
before_action :require_user_signed_in
before_action :require_service_enabled
class Services::RemotestorageController < Services::BaseController
before_action :authenticate_user!
before_action :require_feature_enabled
before_action :set_current_section
before_action :require_service_available
def dashboard
# unless current_user.services_enabled.include?(:remotestorage)
@@ -18,13 +17,7 @@ class Services::RemotestorageController < ApplicationController
end
end
def require_service_enabled
unless Setting.remotestorage_enabled?
http_status :not_found
end
end
def set_current_section
@current_section = :services
def require_service_available
http_status :not_found unless Setting.remotestorage_enabled?
end
end

View File

@@ -1,9 +1,10 @@
import { Application } from "@hotwired/stimulus"
import { Modal } from "tailwindcss-stimulus-components"
import { Modal, Tabs } from "tailwindcss-stimulus-components"
const application = Application.start()
application.register('modal', Modal)
application.register('tabs', Tabs)
// Configure Stimulus development experience
application.debug = false

View File

@@ -51,6 +51,16 @@ class Setting < RailsSettings::Base
field :discourse_connect_secret, type: :string,
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
#
# Drone CI
#
field :droneci_public_url, type: :string,
default: ENV["DRONECI_PUBLIC_URL"].presence
field :droneci_enabled, type: :boolean,
default: (ENV["DRONECI_PUBLIC_URL"].present?.to_s || false)
#
# ejabberd
#
@@ -106,6 +116,9 @@ class Setting < RailsSettings::Base
field :mastodon_enabled, type: :boolean,
default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false)
field :mastodon_address_domain, type: :string,
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
#
# MediaWiki
#

View File

@@ -72,6 +72,7 @@ class User < ApplicationRecord
# E-Mail update confirmed
LdapManager::UpdateEmail.call(self.dn, self.email)
else
# TODO Make configurable
# E-Mail from signup confirmed (i.e. account activation)
enable_service %w[ discourse gitea mediawiki xmpp ]
@@ -109,6 +110,11 @@ class User < ApplicationRecord
"#{self.cn}@#{self.ou}"
end
def mastodon_address
return nil unless Setting.mastodon_enabled?
"#{self.cn}@#{Setting.mastodon_address_domain}"
end
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?

View File

@@ -0,0 +1,16 @@
<h3>Drone CI</h3>
<ul role="list">
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :droneci_enabled,
enabled: Setting.droneci_enabled?,
title: "Enable Drone CI integration",
description: "Drone CI configuration present and features enabled"
) %>
<% if Setting.droneci_enabled? %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :droneci_public_url,
title: "Public URL"
) %>
<% end %>
</ul>

View File

@@ -12,5 +12,9 @@
key: :mastodon_public_url,
title: "Public URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :mastodon_address_domain,
title: "User address domain"
) %>
<% end %>
</ul>

View File

@@ -7,73 +7,85 @@
services:
</p>
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-50px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to "https://wiki.kosmos.org/Services:Chat",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600">
Federated chat rooms and instant messaging
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:95%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600">
Kosmos community forums and user support/help site
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to "https://wiki.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600">
Kosmos documentation and knowledge base
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]">
<%= link_to services_lightning_index_path,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Lightning Network</h3>
<p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to "https://gitea.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600">
Code hosting and collaboration for software projects
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-70px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to "https://drone.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Drone CI</h3>
<p class="text-gray-600">
Continuous integration for software projects on Gitea
</p>
<% end %>
</div>
<% if Setting.remotestorage_enabled? && Flipper.enabled?(:remotestorage, current_user) %>
<% if Setting.ejabberd_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-50px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to services_chat_path,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600">
Federated chat rooms and instant messaging
</p>
<% end %>
</div>
<% end %>
<% if Setting.mastodon_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:80%] bg-[right_top_-30px] bg-no-repeat
bg-[url(/img/logos/icon_mastodon.svg)]">
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Mastodon</h3>
<p class="text-gray-600">
Your account on the Open Social Web
</p>
<% end %>
</div>
<% end %>
<% if Setting.discourse_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:95%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600">
Kosmos community forums and user support/help site
</p>
<% end %>
</div>
<% end %>
<% if Setting.lndhub_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]">
<%= link_to services_lightning_index_path,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Lightning Network</h3>
<p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network
</p>
<% end %>
</div>
<% end %>
<% if Setting.gitea_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to Setting.gitea_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600">
Code hosting and collaboration for software projects
</p>
<% end %>
</div>
<% end %>
<% if Setting.droneci_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-70px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to Setting.droneci_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Drone CI</h3>
<p class="text-gray-600">
Continuous integration for software projects on Gitea
</p>
<% end %>
</div>
<% end %>
<% if Setting.remotestorage_enabled? &&
Flipper.enabled?(:remotestorage, current_user) %>
<div class="border border-gray-300 rounded-md hover:border-gray-400">
<%= link_to services_storage_path,
class: "block h-full px-6 py-6 rounded-md" do %>
@@ -84,16 +96,19 @@
<% end %>
</div>
<% end %>
<!-- <div class="border border&#45;gray&#45;300 rounded&#45;md hover:border&#45;gray&#45;400 -->
<!-- bg&#45;[length:80%] bg&#45;[right_top_&#45;30px] bg&#45;no&#45;repeat -->
<!-- bg&#45;[url(/img/logos/icon_mastodon.svg)]"> -->
<!-- <%= link_to "https://kosmos.social", class: "block h&#45;full px&#45;6 py&#45;6 rounded&#45;md" do %> -->
<!-- <h3 class="mb&#45;3.5">Mastodon</h3> -->
<!-- <p class="text&#45;gray&#45;400"> -->
<!-- Your account on the Open Social Web -->
<!-- </p> -->
<!-- <% end %> -->
<!-- </div> -->
<% if Setting.mediawiki_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to Setting.mediawiki_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600">
Kosmos documentation and knowledge base
</p>
<% end %>
</div>
<% end %>
</div>
</section>
<% end %>

View File

@@ -12,7 +12,8 @@
<div class="mb-6">
<%= f.label :cn, 'User', class: 'block mb-2 font-bold' %>
<p class="flex gap-2 items-center">
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
<%= f.text_field :cn, value: h(params[:cn]),
autofocus: params[:cn].blank?, autocomplete: "username",
required: true, class: "relative grow", tabindex: "1" %>
<span class="relative shrink-0 text-gray-500">@ <%= Setting.primary_domain %></span>
</p>
@@ -20,7 +21,8 @@
<p class="mb-8">
<%= f.label :password, class: 'block mb-2 font-bold' %>
<%= f.password_field :password, autocomplete: "current-password",
required: true, class: "w-full", tabindex: "2" %>
autofocus: params[:cn].present?, required: true,
class: "w-full", tabindex: "2" %>
</p>
<%= tag.div class: "flex items-center mb-8 gap-x-3", data: {

View File

@@ -8,7 +8,9 @@
</p>
<ul class="md:w-3/4">
<% @invitations_unused.each do |invitation| %>
<li class="mb-3 flex gap-1" data-controller="clipboard modal">
<li class="mb-3 flex gap-1"
data-controller="clipboard modal"
data-action="keydown.esc->modal#close">
<input type="text" disabled class="relative grow font-mono"
value="<%= invitation_url(invitation.token) %>"
data-clipboard-target="source" />

View File

@@ -1,7 +1,7 @@
<%= render HeaderCompactComponent.new(title: "Storage") %>
<%= render MainCompactComponent.new do %>
<section>
<section class="permissions">
<p class="mb-8">
The app on
<%= link_to @client_id, "https://#{@client_id}", class: "ks-text-link" %>
@@ -9,7 +9,7 @@
</p>
<% if @root_access_requested %>
<p class="text-lg">
<p class="scope text-lg">
<span class="text-red-700">
<%= render partial: "icons/alert-triangle",
locals: { custom_class: "inline-block align-bottom mr-1.5" } %>
@@ -21,7 +21,7 @@
</p>
<% else %>
<% @scopes.each do |scope| %>
<p class="text-gray-600">
<p class="scope text-gray-600">
<span class="text-lg">
<%= render partial: "icons/folder",
locals: { custom_class: "inline-block align-bottom mr-1.5" } %>

View File

@@ -0,0 +1,199 @@
<%= render HeaderComponent.new(title: "Chat") %>
<%= render MainSimpleComponent.new do %>
<section>
<p class="mb-6">
Chat with anyone on the open Jabber (XMPP) network. Message people directly, or
join public channels or private rooms.
</p>
</section>
<section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Your Chat Address</h3>
<p class="mb-6">
When you exchange contacts with people, give them your
address, or add them using their address:
</p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
<input type="text" id="user_address" class="grow"
value=<%= current_user.address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
data-action="click->modal#open" title="Show QR code">
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</button>
</p>
<%= render QrCodeModalComponent.new(qr_content: "xmpp:"+current_user.address) %>
</section>
<section>
<h3>Chat Apps</h3>
<p>
Use your account with many different apps, and on any devices you wish!
When opening an app for the first time, just enter your user address and
password to log in.
</p>
</section>
<section>
<h3>Recommended Apps</h3>
<div data-controller="tabs"
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
class="mb-12">
<select data-action="tabs#change" data-tabs-target="select"
class="block w-full mb-8 sm:hidden">
<optgroup label="Mobile">
<option>Android</option>
<option>iOS</option>
</optgroup>
<optgroup label="Desktop">
<option>Linux</option>
<option>Windows</option>
<option>macOS</option>
</optgroup>
</select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Android
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
iOS
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS
</a>
</li>
<!-- <li class="mr&#45;2" data&#45;tabs&#45;target="tab" data&#45;action="click&#45;>tabs#change"> -->
<!-- <a href="#" class="bg&#45;white inline&#45;block py&#45;2 px&#45;4 font&#45;semibold no&#45;underline"> -->
<!-- Web -->
<!-- </a> -->
<!-- </li> -->
</ul>
<div id="apps-android" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Conversations",
description: "The gold standard for Jabber on mobile devices",
icon_path: "/img/logos/icon_conversations.png",
links: [
["Website", "https://conversations.im"],
["Google Play", "https://play.google.com/store/apps/details?id=eu.siacs.conversations"],
["F-Droid", "https://f-droid.org/en/packages/eu.siacs.conversations/"],
]
) %>
</div>
<div id="apps-ios" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Siskin IM",
description: "Lightweight and powerful chat app for iPhone and iPad",
icon_path: "/img/logos/logo_siskin.png",
links: [
["Website", "https://siskin.im"],
["App Store", "https://apps.apple.com/us/app/tigase-messenger/id1153516838"]
]
) %>
<%= render AppInfoComponent.new(
name: "Monal",
description: "A chat app for iOS, iPadOS, and macOS",
icon_path: "/img/logos/icon_monal.svg",
icon_fill_box: true,
links: [
["Website", "https://monal-im.org"],
["App Store", "https://apps.apple.com/app/id317711500"]
]
) %>
</div>
<div id="apps-linux" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Dino",
description: "A modern and simple chat app for Linux (good for GNOME)",
icon_path: "/img/logos/icon_dino.svg",
links: [
["Website", "https://dino.im"],
["Install from package", "https://github.com/dino/dino/wiki/Distribution-Packages"]
]
) %>
<%= render AppInfoComponent.new(
name: "Kaidan",
description: "A fairly new, user-friendly chat app for all devices (good for KDE)",
icon_path: "/img/logos/icon_kaidan.svg",
links: [
["Website", "https://kaidan.im"],
]
) %>
<%= render AppInfoComponent.new(
name: "Gajim",
description: "A fully-featured chat app for Linux and Windows",
icon_path: "/img/logos/icon_gajim.png",
links: [
["Website", "https://gajim.org/"]
]
) %>
</div>
<div id="apps-windows" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Gajim",
description: "A fully-featured chat app for Linux and Windows",
icon_path: "/img/logos/icon_gajim.png",
links: [
["Website", "https://gajim.org/"],
["Microsoft Store", "https://apps.microsoft.com/store/detail/9PGGF6HD43F9?launch=true&mode=mini"],
["Download options", "https://gajim.org/download/"]
]
) %>
</div>
<div id="apps-mac" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Beagle IM",
description: "Lightweight and powerful chat app for macOS",
icon_path: "/img/logos/logo_beagle.png",
links: [
["Website", "https://beagle.im"],
["App Store", "https://apps.apple.com/us/app/beagleim-by-tigase-inc/id1445349494"]
]
) %>
<%= render AppInfoComponent.new(
name: "Monal",
description: "A chat app for iOS, iPadOS, and macOS",
icon_path: "/img/logos/icon_monal.svg",
icon_fill_box: true,
links: [
["Website", "https://monal-im.org"],
["App Store", "https://apps.apple.com/app/id1637078500"]
]
) %>
</div>
<!-- <div class="hidden grid grid&#45;cols&#45;1 gap&#45;4 sm:gap&#45;6" data&#45;tabs&#45;target="panel"> -->
<!-- Web -->
<!-- </div> -->
</div>
</section>
<% end %>

View File

@@ -29,7 +29,7 @@
</p>
</section>
<section data-controller="modal">
<section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Wallet Apps</h3>
<p>
You can connect various wallet apps to your Kosmos account. This allows

View File

@@ -0,0 +1,219 @@
<%= render HeaderComponent.new(title: "Social") %>
<%= render MainSimpleComponent.new do %>
<section>
<p class="mb-6">
Follow and interact with anyone on the open social web, from your Kosmos Mastodon account.
</p>
</section>
<section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Your User Address</h3>
<p class="mb-6">
Others can follow you under this address:
</p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
<input type="text" id="user_address" class="grow"
value=<%= current_user.mastodon_address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
data-action="click->modal#open" title="Show QR code">
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</button>
</p>
<%= render QrCodeModalComponent.new(qr_content: current_user.address) %>
</section>
<section>
<h3>Social Apps</h3>
<p>
Use your Mastodon account with many different apps, and on any devices
you wish! When adding your account to an app, you will log in via
<a href="https://kosmos.social" target="_blank" class="ks-text-link">kosmos.social</a>.
</p>
</section>
<section>
<h3>Recommended Apps</h3>
<div data-controller="tabs"
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
class="mb-12">
<select data-action="tabs#change" data-tabs-target="select"
class="block w-full mb-8 sm:hidden">
<option>Web</option>
<optgroup label="Mobile">
<option>Android</option>
<option>iOS</option>
</optgroup>
<optgroup label="Desktop">
<option>Linux</option>
<option>Windows</option>
<option>macOS</option>
</optgroup>
</select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Web
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-5 font-semibold no-underline">
Android
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
iOS
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS
</a>
</li>
</ul>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "kosmos.social",
description: "The official Web app",
icon_path: "/img/logos/icon_mastodon-2.svg",
links: [
["Launch", "https://kosmos.social"]
]
) %>
<%= render AppInfoComponent.new(
name: "Elk",
description: " A nimble Mastodon web client",
icon_path: "/img/logos/icon_elk.svg",
links: [
["Launch", "https://elk.zone"],
["GitHub", "https://github.com/elk-zone/elk"]
]
) %>
<%= render AppInfoComponent.new(
name: "Sengi",
description: "A cross-platform app, inspired by TweetDeck",
icon_path: "/img/logos/icon_sengi.png",
links: [
["Website", "https://nicolasconstant.github.io/sengi/"],
["GitHub", "https://github.com/NicolasConstant/sengi"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Mastodon for Android",
description: "Android client by the Mastodon core team",
icon_path: "/img/logos/icon_mastodon-2.svg",
links: [
["Website", "https://joinmastodon.org/apps"],
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android"]
]
) %>
<%= render AppInfoComponent.new(
name: "Fedilab",
description: "Android client with many features",
icon_path: "/img/logos/icon_fedilab.png",
links: [
["Website", "https://fedilab.app"],
["Google Play", "https://play.google.com/store/apps/details?id=app.fedilab.android"],
["F-Droid", "https://f-droid.org/packages/fr.gouv.etalab.mastodon"],
]
) %>
<%= render AppInfoComponent.new(
name: "Megalodon",
description: "A popular fork of the official Android app",
icon_path: "/img/logos/icon_megalodon.png",
icon_fill_box: true,
links: [
["Website", "https://sk22.github.io/megalodon/"],
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Mastodon for iOS",
description: "iOS client by the Mastodon core team",
icon_path: "/img/logos/icon_mastodon-2.svg",
links: [
["Website", "https://joinmastodon.org/apps"],
["App Store", "https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974"]
]
) %>
<%= render AppInfoComponent.new(
name: "Ice Cubes",
description: "Slick, fast, open source, and with customizable UI",
icon_path: "/img/logos/icon_icecubes.png",
icon_fill_box: true,
links: [
["App Store", "https://apps.apple.com/us/app/ice-cubes-for-mastodon/id6444915884"],
["GitHub", "https://github.com/Dimillian/IceCubesApp"]
]
) %>
<%= render AppInfoComponent.new(
name: "Mammoth",
description: " Powerful, fast, feature-rich",
icon_path: "/img/logos/icon_mammoth.png",
links: [
["Website", "https://getmammoth.app/"],
["App Store", "https://apps.apple.com/app/mammoth-for-mastodon/id1667573899"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Tuba",
description: "A simple, fast Mastodon app for Linux (good on GNOME)",
icon_path: "/img/logos/icon_tuba.svg",
links: [
["Website", "https://tuba.geopjr.dev"],
["Flathub", "https://flathub.org/apps/dev.geopjr.Tuba"],
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Sengi",
description: "A cross-platform app, inspired by TweetDeck",
icon_path: "/img/logos/icon_sengi.png",
links: [
["Website", "https://nicolasconstant.github.io/sengi/"],
["GitHub", "https://github.com/NicolasConstant/sengi"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Mastonaut",
description: "Simple, elegant, and native Mastodon client for Mac",
icon_path: "/img/logos/icon_mastonaut.png",
links: [
["Launch", "https://www.mastonaut.app"],
["Mac App Store", "https://apps.apple.com/app/mastonaut/id1450757574"]
]
) %>
</div>
</div>
</section>
<% end %>

View File

@@ -5,6 +5,13 @@
icon: Setting.discourse_enabled? ? "check" : "x",
active: current_page?(admin_settings_services_path(params: { s: "discourse" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Drone CI",
path: admin_settings_services_path(params: { s: "droneci" }),
icon: Setting.droneci_enabled? ? "check" : "x",
active: current_page?(admin_settings_services_path(params: { s: "droneci" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "ejabberd",

View File

@@ -0,0 +1,6 @@
<%= render HeaderCompactComponent.new(title: "404") %>
<%= render MainCompactComponent.new do %>
<h2>Bad request</h2>
<p>Please go back and try again.</p>
<% end %>

View File

@@ -6,4 +6,4 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "bech32" # @2.0.0
pin "tailwindcss-stimulus-components" # @3.0.4
pin "tailwindcss-stimulus-components" # @4.0.3

View File

@@ -21,12 +21,16 @@ Rails.application.routes.draw do
namespace :services do
get 'storage', to: 'remotestorage#dashboard'
resource :chat, only: [:show], controller: 'chat'
resources :lightning, only: [:index] do
collection do
get 'transactions'
get 'qr_lnurlp'
end
end
resource :mastodon, only: [:show], controller: 'mastodon'
end
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
@@ -67,8 +71,9 @@ Rails.application.routes.draw do
end
namespace :rs do
resource :oauth, only: [:new, :create], path_names: { new: ':useraddress' },
controller: 'oauth', constraints: { useraddress: /[^\/]+/}
resource :oauth, only: [:new, :create], path_names: {
new: ':useraddress', create: ':useraddress'
}, controller: 'oauth', constraints: { useraddress: /[^\/]+/}
get 'oauth/token/:id/launch_app' => 'oauth#launch_app', as: :launch_app
end

View File

@@ -1,9 +1,12 @@
class CreateRemoteStorageAuthorizations < ActiveRecord::Migration[7.0]
def change
db_type = ActiveRecord::Base.configurations.find_db_config(Rails.env).adapter
array_default = db_type == "postgresql" ? [] : [].to_yaml
create_table :remote_storage_authorizations do |t|
t.references :user, null: false, foreign_key: true
t.string :token
t.text :permissions, array: true, default: [].to_yaml
t.text :permissions, array: true, default: array_default
t.string :client_id
t.string :redirect_uri
t.string :app_name

View File

@@ -84,13 +84,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
t.datetime "confirmed_at", precision: nil
t.datetime "confirmation_sent_at", precision: nil
t.string "unconfirmed_email"
t.text "ln_login_ciphertext"
t.text "ln_password_ciphertext"
t.string "ln_account"
t.string "nostr_pubkey"
t.datetime "remember_created_at"
t.string "remember_token"
t.text "preferences"
t.string "nostr_pubkey"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

View File

@@ -11,7 +11,7 @@
"postcss-preset-env": "^7.8.3",
"tailwindcss": "^3.2.4"
},
"version": "0.7.0",
"version": "0.8.1",
"scripts": {
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
"build:css": "yarn run build:css:tailwind"

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="250"
height="250"
fill="none"
version="1.1"
id="svg30"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs34" />
<mask
id="a"
width="240"
height="234"
x="4"
y="1"
maskUnits="userSpaceOnUse"
style="mask-type:alpha">
<path
fill="white"
d="M244 123c0 64.617-38.383 112-103 112-64.617 0-103-30.883-103-95.5C38 111.194-8.729 36.236 8 16 29.46-9.959 88.689 6 125 6c64.617 0 119 52.383 119 117Z"
id="path19" />
</mask>
<g
mask="url(#a)"
id="g28"
transform="matrix(0.90923731,0,0,1.0049564,13.520015,-3.1040835)">
<path
fill="#ea9e44"
d="m 116.94,88.1 c -13.344,1.552 -20.436,-2.019 -24.706,10.71 0,0 14.336,21.655 52.54,21.112 -2.135,8.848 -1.144,15.368 -1.144,23.207 0,26.079 -20.589,48.821 -65.961,48.821 -23.03,0 -51.015,4.191 -72.367,15.911 -15.175,8.305 -27.048,20.336 -32.302,37.023 l 5.956,8.461 11.4,0.155 v 47.889 l -13.91,21.966 3.998,63.645 H -6.364 L -5.22,335.773 C 1.338,331.892 16.36,321.802 29.171,306.279 46.557,285.4 59.902,255.052 44.193,217.486 l 11.744,-5.045 c 12.887,30.814 8.388,57.514 -2.898,79.013 21.58,-0.698 40.11,-2.095 55.819,-4.734 l -3.584,-43.698 12.659,-1.087 L 129.98,387 h 13.116 l 2.212,-94.459 c 10.447,-4.502 34.239,-21.034 45.372,-78.47 1.372,-6.986 2.135,-12.885 2.516,-17.93 1.754,-12.806 2.745,-27.243 3.051,-43.698 l -18.683,-5.976 h 57.42 l 5.567,-12.807 c -5.414,0.233 -11.896,-2.639 -11.896,-2.639 l 1.297,-6.209 H 242 L 176.801,90.428 c -7.244,2.794 -14.87,6.442 -20.208,10.866 -4.27,-3.105 -19.063,-12.807 -39.653,-13.195 z"
id="path22" />
<path
fill="#c16929"
d="M 6.217,24.493 18.494,21 c 5.948,21.577 13.345,33.375 22.648,39.352 8.388,5.099 19.75,5.239 31.799,4.579 C 69.433,63.767 66.154,62.137 63.104,59.886 56.317,54.841 50.522,46.458 46.175,31.246 l 12.201,-3.649 c 3.279,11.488 7.092,18.085 12.201,21.888 5.11,3.726 11.286,4.657 18.606,5.433 13.726,1.553 30.884,2.174 52.312,12.264 2.898,1.086 5.872,2.483 8.769,4.036 -0.381,-0.776 -0.762,-1.553 -1.296,-2.406 -3.66,-5.822 -10.828,-11.953 -24.097,-16.92 l 4.27,-12.109 c 21.581,7.917 30.121,19.171 33.553,28.097 3.965,10.168 1.525,18.124 1.525,18.124 -3.05,1.009 -6.1,2.406 -9.608,3.492 -6.634,-4.579 -12.887,-8.033 -18.835,-10.75 C 113.814,70.442 92.31,76.108 73.246,77.893 58.91,79.213 45.794,78.591 34.432,71.295 23.222,64.155 13.385,50.495 6.217,24.493 Z"
id="path24" />
<path
fill="#c16929"
d="M 90.098,45.294 C 87.582,39.55 86.057,32.487 86.743,23.794 l 12.659,0.932 c -0.763,10.555 2.897,17.696 7.015,22.353 -5.338,-0.931 -10.447,-1.04 -16.319,-1.785 z m 80.069,-1.32 8.312,-9.702 c 21.58,19.094 8.159,46.415 8.159,46.415 l -11.819,-1.32 c -0.382,-6.24 -1.144,-17.836 -6.635,-24.371 3.584,1.84 6.635,3.865 9.99,6.908 0,-5.666 -1.754,-12.341 -8.007,-17.93 z"
id="path26" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="256.39" width="256.39"><defs><radialGradient xlink:href="#b" id="g" cx="97.347" cy="925.562" fx="97.347" fy="925.562" r="59.347" gradientTransform="matrix(0 .94357 -2.19206 0 2158.799 18.862)" gradientUnits="userSpaceOnUse"/><radialGradient xlink:href="#b" id="h" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .8425 -1.93775 0 1946.71 -14.002)" cx="95.806" cy="937.536" fx="95.806" fy="937.536" r="59.347"/><radialGradient id="a" gradientUnits="userSpaceOnUse" cy="256" cx="256" gradientTransform="matrix(2.2585 .02063 -.02134 2.336 -316.73 -347.29)" r="249.14"><stop stop-color="#30c357" offset="0"/><stop offset=".5" stop-color="#2cb465"/><stop stop-color="#32a482" offset="1"/></radialGradient><radialGradient xlink:href="#b" id="f" cx="88.754" cy="123.035" fx="88.754" fy="123.035" r="78.3" gradientTransform="matrix(.02885 1.09003 -1.35878 .03597 300.127 33.098)" gradientUnits="userSpaceOnUse"/><linearGradient id="b"><stop offset="0" stop-opacity=".416"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient xlink:href="#a" id="e" gradientUnits="userSpaceOnUse" x1="127.988" y1="808.969" x2="127.988" y2="1033.499" gradientTransform="translate(18.596 -806.496) scale(1.011)"/><linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" x1="127.988" y1="808.969" x2="127.988" y2="1033.499" gradientTransform="translate(18.596 -806.496) scale(1.011)"/><filter id="c" x="-.041" width="1.082" y="-.04" height="1.081" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="3.891"/></filter></defs><g transform="translate(82 -2.286)" opacity=".512" fill-opacity=".509" filter="url(#c)"><path d="M47.992 16.371c-62.684 0-113.5 50.816-113.5 113.5 0 15.92 3.284 31.071 9.201 44.822 2.875 7.87 5.724 15.722 6.72 22.264 1.01 6.646.108 11.944-1.16 15.361-1.266 3.418-2.901 4.955-4.165 8.502-1.264 3.548-2.155 9.106.775 13.319 2.93 4.213 9.68 7.08 22.17 9.277 12.49 2.197 30.725 3.719 46.623 3.719 15.899 0 29.471-1.536 39.037-2.932 8.418-1.228 13.704-2.35 18.643-3.459.557-.122 1.114-.241 1.668-.37l.363-.083-.01-.006c49.969-11.888 87.135-56.807 87.135-110.414 0-62.684-50.816-113.5-113.5-113.5z" fill-opacity="1" paint-order="stroke fill markers"/></g><g fill="#1f6d52"><path d="M241.492 125.586c0 62.684-50.816 113.5-113.5 113.5s-113.5-50.816-113.5-113.5 50.816-113.5 113.5-113.5 113.5 50.816 113.5 113.5z" paint-order="stroke fill markers"/><path d="M154.368 236.005c-5.556 1.258-11.108 2.516-20.674 3.912-9.566 1.397-23.139 2.93-39.037 2.933-15.899 0-34.133-1.524-46.623-3.72-12.49-2.197-19.241-5.064-22.171-9.277-2.93-4.213-2.038-9.77-.774-13.318 1.263-3.548 2.898-5.086 4.165-8.503 1.268-3.418 2.168-8.716 1.158-15.361-1.011-6.646-3.932-14.64-6.853-22.634"/></g><path d="M261.492 124.872c0 62.684-50.816 113.5-113.5 113.5s-113.5-50.816-113.5-113.5c0-62.685 50.816-113.5 113.5-113.5s113.5 50.815 113.5 113.5z" fill="url(#d)" paint-order="stroke fill markers" transform="translate(-20 -4.286)"/><path d="M174.368 235.29c-5.556 1.26-11.108 2.517-20.674 3.913-9.566 1.396-23.139 2.93-39.037 2.933-15.899 0-34.133-1.524-46.623-3.72-12.49-2.197-19.241-5.064-22.101-8.228-2.86-3.163-1.828-6.622-.565-10.17 1.264-3.547 2.759-7.184 3.956-11.651 1.198-4.467 2.098-9.765 1.088-16.41-1.011-6.646-3.932-14.64-6.853-22.635" fill="url(#e)" transform="translate(-20 -4.286)"/><path d="M188.207 160.324l-82.176 19.83-19.979-20.038S72.97 173.377 67.98 179.894l46.02 46.82 14.242 7.371c10.099-1.39 19.726-3.117 28.797-5.267.013 0 .026-.01.04-.01 27.199-7.275 48.656-19.338 63.52-36.12z" fill="url(#f)"/><path d="M205.473 113.95L49.639 132.596l30 30 147.375 21.975c7.02-9.995 12.02-21.406 14.867-34.211z" fill="url(#g)"/><path d="M188.46 68.752l45 45-142.557-2.826-24-24z" fill="url(#h)"/><g transform="translate(-88.78 -687.44) scale(1.011)" fill="#c1ede5"><rect x="150.87" y="744.15" width="127.26" height="26.476" ry="13.238" rx="13.238"/><rect x="133.62" y="789.12" width="161.61" height="26.476" ry="13.238" rx="13.238"/><rect x="186.23" y="834.92" width="91.833" height="26.476" ry="13.238" rx="13.238"/><rect x="150.8" y="834.92" width="26.476" height="26.476" ry="13.238" rx="13.238"/></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
version="1.1"
id="svg13"
sodipodi:docname="icon_mastodon-2.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview15"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="10.329114"
inkscape:cx="25.849265"
inkscape:cy="39.548407"
inkscape:window-width="1663"
inkscape:window-height="749"
inkscape:window-x="50"
inkscape:window-y="118"
inkscape:window-maximized="0"
inkscape:current-layer="svg13" />
<a
id="a300"
transform="translate(12.930783,10.499984)">
<path
d="M 73.8393,17.4898 C 72.6973,9.00165 65.2994,2.31235 56.5296,1.01614 55.05,0.797115 49.4441,0 36.4582,0 h -0.097 C 23.3717,0 20.585,0.797115 19.1054,1.01614 10.5798,2.27644 2.79399,8.28712 0.904997,16.8758 -0.00358524,21.1056 -0.100549,25.7949 0.0682394,30.0965 0.308852,36.2651 0.355538,42.423 0.91577,48.5665 c 0.3873,4.0809 1.06295,8.1292 2.02186,12.1147 1.79562,7.3608 9.06427,13.4864 16.18567,15.9854 7.6245,2.6062 15.8241,3.0389 23.6806,1.2496 0.8643,-0.2011 1.7178,-0.4345 2.5606,-0.7002 1.9105,-0.6068 4.1478,-1.2855 5.7926,-2.4775 0.0226,-0.0168 0.0411,-0.0384 0.0541,-0.0632 0.0131,-0.0249 0.0204,-0.0524 0.0213,-0.0805 v -5.9532 c -4e-4,-0.0262 -0.0066,-0.052 -0.0183,-0.0755 -0.0117,-0.0235 -0.0284,-0.0441 -0.0491,-0.0603 -0.0207,-0.0162 -0.0447,-0.0275 -0.0703,-0.0332 -0.0256,-0.0057 -0.0522,-0.0056 -0.0777,3e-4 -5.0336,1.2021 -10.1917,1.8048 -15.3669,1.7953 -8.9063,0 -11.3016,-4.2262 -11.9876,-5.9856 -0.5513,-1.5206 -0.9014,-3.1067 -1.0414,-4.718 -0.0015,-0.0271 0.0035,-0.0541 0.0145,-0.0789 0.0109,-0.0248 0.0276,-0.0466 0.0486,-0.0637 0.021,-0.0172 0.0457,-0.0291 0.0722,-0.0349 0.0265,-0.0058 0.0539,-0.0053 0.0802,0.0015 4.9497,1.194 10.0237,1.7967 15.1155,1.7953 1.2246,0 2.4456,0 3.6702,-0.0323 5.1211,-0.1436 10.5187,-0.4057 15.5572,-1.3895 0.1257,-0.0252 0.2514,-0.0467 0.3591,-0.079 7.9474,-1.5261 15.5106,-6.3159 16.2791,-18.445 0.0287,-0.4775 0.1006,-5.0017 0.1006,-5.4972 0.0035,-1.684 0.5422,-11.946 -0.0791,-18.2511 z"
fill="url(#paint0_linear_549_34)"
id="path2"
style="fill:url(#paint0_linear_549_34)" />
<path
d="M 61.2484,27.0263 V 48.114 H 52.8916 V 27.6475 c 0,-4.3087 -1.7956,-6.5062 -5.4479,-6.5062 -4.015,0 -6.026,2.5996 -6.026,7.7342 V 40.0782 H 33.1111 V 28.8755 c 0,-5.1346 -2.0146,-7.7342 -6.0296,-7.7342 -3.6308,0 -5.4444,2.1975 -5.4444,6.5062 V 48.114 H 13.2839 V 27.0263 c 0,-4.3087 1.1001,-7.7317 3.3004,-10.2691 2.2696,-2.5314 5.2468,-3.8312 8.9421,-3.8312 4.2772,0 7.5093,1.6445 9.6641,4.9299 l 2.0793,3.4901 2.0829,-3.4901 c 2.1547,-3.2854 5.3868,-4.9299 9.6568,-4.9299 3.6918,0 6.6689,1.2998 8.9458,3.8312 2.1978,2.535 3.2955,5.958 3.2931,10.2691 z"
fill="#ffffff"
id="path4" />
</a>
<defs
id="defs11">
<linearGradient
id="paint0_linear_549_34"
x1="37.069199"
y1="0"
x2="37.069199"
y2="79"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#6364FF"
id="stop6" />
<stop
offset="1"
stop-color="#563ACC"
id="stop8" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20mm"
height="20.085215mm"
viewBox="0 0 20 20.085215"
version="1.1"
id="svg3834"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="monal.svg"
style="enable-background:new"
inkscape:export-filename="/Users/anurodhp/Desktop/monal.png"
inkscape:export-xdpi="1300.48"
inkscape:export-ydpi="1300.48">
<defs
id="defs3828">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 10.042608 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="20 : 10.042608 : 1"
inkscape:persp3d-origin="10 : 6.6950717 : 1"
id="perspective841" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="9.0725624"
inkscape:cx="28.787632"
inkscape:cy="44.279616"
inkscape:document-units="mm"
inkscape:current-layer="layer3"
showgrid="false"
inkscape:window-width="1280"
inkscape:window-height="700"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata3831">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-181.15717,-221.0978)"
style="display:inline">
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Layer 2"
style="display:inline">
<rect
id="rect30"
width="20"
height="20"
x="181.15717"
y="221.18301"
style="stroke-width:0.26458332" />
<rect
style="fill:#2cd3e3;fill-opacity:1;stroke-width:0.26458332;opacity:1"
id="rect48"
width="20"
height="20"
x="181.15717"
y="221.18301"
inkscape:export-xdpi="1299.6801"
inkscape:export-ydpi="1299.6801" />
</g>
<g
id="g25"
transform="matrix(1.25,0,0,1.25,-45.477532,-55.597438)">
<g
id="g4578"
transform="translate(0.8477441,0.34537723)">
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:0.21166666"
d="m 191.56803,221.43167 c 0.44531,-0.54927 1.00283,-1.55943 1.42657,-1.47418 -1.6117,3.28945 -5.09363,5.55983 -6.77643,8.41276 -1.48053,1.02259 -5.59221,4.97552 -3.50788,4.62647 2.55426,-0.33289 4.26942,-0.18434 6.47037,-0.0863 2.97857,0.44913 2.99008,0.35922 1.42237,-0.94939 -4.08407,-2.92741 -7.3886,-5.96422 -2.55345,-2.51434 1.19608,0.89899 3.58977,2.88011 4.62961,3.96256 0.14192,0.38081 -1.36875,-1.03083 -1.63269,-1.38151 1.00787,0.47021 4.41857,3.94275 5.65285,3.45204 1.11708,-0.36357 1.5434,-1.4884 2.64165,-0.91497 0.73976,-0.52896 -0.81203,-1.84714 0.109,-2.45498 1.18118,-0.18435 -0.2902,-1.51785 0.93686,-1.87197 0.72674,-0.62762 -1.09372,-1.01894 -1.52077,-1.45287 -0.89602,-0.45402 -0.35512,-1.55824 -0.004,-1.79086 0.0846,-1.03756 1.78245,-0.86434 2.36976,-1.66716 0.74024,-0.20999 1.24713,-1.13045 0.10892,-1.05465 -1.29104,-0.33585 -2.48308,0.3858 -3.65095,0.80824 -0.75744,0.43619 -1.51475,-0.3745 -0.67602,-0.9122 0.086,-0.87983 -0.49258,-4.71845 2.78132,-3.06301 0.34122,0.76448 -1.29924,0.73222 -1.56848,1.44997 -0.51725,0.58889 -0.82268,1.36738 -0.81609,2.1534 1.37222,-0.61392 1.59347,-2.39848 2.89681,-3.09724 0.62326,-0.9785 1.02124,0.86222 0.0967,0.71688 -0.41512,0.46396 -1.82667,1.32778 -1.43703,1.64655 0.99965,-0.74709 2.16479,-0.20775 3.17351,0.0358 1.13327,0.97208 -0.7695,1.66177 -1.40739,2.19644 -0.72103,0.3951 -1.48929,0.73257 -1.53088,1.60897 -0.81354,-0.32254 0.12422,1.14184 0.65017,1.27543 0.43315,0.20734 0.91104,0.44698 0.50741,-0.12798 0.30174,-0.53571 1.10164,0.28036 0.4594,-0.4453 0.0479,-1.20018 1.96828,-0.76448 2.64659,-1.55994 1.00428,-0.71951 -0.0847,-1.22662 -0.85018,-1.0046 -0.50182,0.06 -1.07873,-0.43882 -0.22366,-0.39397 0.83328,-0.14122 2.91477,0.25377 1.94632,1.4131 -1.06336,0.52011 -1.39764,1.49535 -1.68824,2.5366 -0.64436,0.88909 0.0567,1.65993 0.51433,2.21278 -0.0755,1.02463 -0.9696,1.82839 -1.60005,2.58943 -0.34214,0.34256 -1.17179,0.99352 -0.4029,0.26644 0.9274,-1.03713 1.17061,-2.97085 -0.091,-3.84667 -0.84429,-0.40075 -0.98573,0.46872 -0.61694,1.00935 -0.25867,0.8 -0.87545,1.54176 -0.22698,2.35526 -0.13692,1.16852 -1.3052,0.0978 -1.98479,0.71485 -0.94554,0.26803 -0.56905,0.54435 -1.5424,0.66888 1.68588,0.55388 2.10672,-0.0925 3.78947,-0.34726 -0.64558,0.75646 -1.80506,1.10898 -2.81213,1.13009 -2.03076,-0.002 -3.80891,-1.10663 -5.61502,-1.89118 -1.12924,-0.54753 -4.82644,-1.3203 -6.13091,-1.1211 -1.74005,0.0829 -1.83717,0.0547 -3.444,0.29301 -1.26438,-0.004 -1.42104,-0.75538 -0.51486,-2.02424 3.57949,-3.11534 6.69133,-7.03177 9.59613,-10.08739 z m -1.14004,8.46391 c -6.64532,4.78894 -3.32266,2.39447 0,0 z m -1.20322,0.0301 c -1.1412,-0.63874 -0.31307,-2.07894 0.17279,-2.89173 0.89169,-1.36972 2.86985,-1.39236 4.16156,-0.61159 1.54938,1.0298 2.07366,1.03412 2.06974,1.79612 -1.4368,0.34261 -2.55154,1.41885 -3.96896,1.80186 -0.79909,0.16878 -1.64355,0.0662 -2.43562,-0.0947 z m 3.57167,-0.77942 c 1.35312,-0.0504 0.6421,-1.87946 -0.27756,-2.06232 -2.33109,-0.63103 -2.8492,0.97005 -3.34013,1.46545 0.0455,1.37625 2.35919,1.3962 3.29693,0.77529 0.10657,-0.0601 -0.10797,0.0576 0,0 z m -3.61769,-0.59687 c -5.81249,5.68643 -2.90624,2.84321 0,0 z m 4.87351,-8.29404 c -9.0615,11.21579 -4.53075,5.60789 0,0 z"
id="path3882"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
</g>
</g>
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
id="path4583"
cx="185.16042"
cy="234.62871"
rx="0.74570084"
ry="0.51021636" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 3"
style="display:inline">
<path
style="opacity:1;vector-effect:none;fill:#2cd3e3;fill-opacity:1;fill-rule:evenodd;stroke-width:0.43717846;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path884"
sodipodi:type="arc"
sodipodi:cx="7.5222836"
sodipodi:cy="13.490612"
sodipodi:rx="3.1455584"
sodipodi:ry="1.9101746"
sodipodi:start="0"
sodipodi:end="6.1045858"
sodipodi:open="true"
d="M 10.667842,13.490612 A 3.1455584,1.9101746 0 0 1 7.6626857,15.398883 3.1455584,1.9101746 0 0 1 4.3892589,13.660964 3.1455584,1.9101746 0 0 1 7.102196,11.597548 3.1455584,1.9101746 0 0 1 10.617807,13.151267"
transform="matrix(0.93916761,-0.34345916,0.51106856,0.85953995,0,0)" />
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Layer 4"
style="display:inline">
<circle
style="opacity:1;vector-effect:none;fill:#b3ff80;fill-opacity:1;fill-rule:evenodd;stroke-width:0.38550848;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path907"
cx="13.877442"
cy="8.9755268"
r="1.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -0,0 +1,465 @@
require 'rails_helper'
RSpec.describe Rs::OauthController, type: :controller do
let(:user) { create :user }
describe "GET /rs/oauth/:useraddress" do
context "when user is signed in" do
before do
sign_in user
end
context "when username is different than current user" do
let(:other_user) { create :user, id: 23, cn: "jomokenyatta", email: "jomo@hotmail.com" }
before do
get :new, params: {
useraddress: other_user.address,
redirect_uri: "https://example.com",
client_id: "example.com",
scope: "examples"
}
end
it "logs out the users and repeats the request" do
url = new_rs_oauth_url other_user.address,
redirect_uri: "https://example.com",
client_id: "example.com",
scope: "examples"
expect(response).to redirect_to(url)
end
end
context "when no valid token exists" do
before do
get :new, params: {
useraddress: user.address,
redirect_uri: "https://example.com",
client_id: "example.com",
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
state: "foobar123"
}
end
it "returns a 200" do
expect(response.response_code).to eq(200)
end
it "sets the instance variables" do
expected_scopes = %w(documents photos contacts:rw videos:r tasks/work:r)
expect(assigns["user"]).to eq(user)
expect(assigns["redirect_uri"]).to eq("https://example.com")
expect(assigns["scopes"]).to eq(expected_scopes)
expect(assigns["client_id"]).to eq("example.com")
expect(assigns["root_access_requested"]).to eq(false)
expect(assigns["state"]).to eq("foobar123")
expect(assigns["denial_url"]).to eq("https://example.com#error=access_denied&state=foobar123")
end
context "no redirect_uri" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
client_id: "https://example.com"
}
end
it "returns a 400" do
expect(response.response_code).to eq(400)
end
end
context "no client_id" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com"
}
end
it "redirects with invalid_request error" do
expect(response).to redirect_to("https://example.com#error=invalid_request")
end
end
context "different host for client_id and redirect_uri" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com/foobar",
client_id: "https://google.com"
}
end
it "redirects with invalid_client error" do
expect(response).to redirect_to("https://example.com/foobar#error=invalid_client")
end
end
end
context "when valid token already exists" do
before do
@auth = user.remote_storage_authorizations.create!(
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
client_id: "example.com", redirect_uri: "https://example.com",
expire_at: 1.day.from_now
)
end
after { @auth.destroy }
context "with same host for client_id and redirect_uri" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com",
client_id: "https://example.com"
}
end
it "redirects to the redirect_uri with the existing token" do
expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}")
end
end
context "with different host for client_id and redirect_uri" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://app.example.com",
client_id: "https://example.com"
}
end
it "redirects with invalid_client error" do
expect(response).to redirect_to("https://app.example.com#error=invalid_client")
end
end
context "with different redirect_uri" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com/a_new_route",
client_id: "https://example.com"
}
end
it "redirects to the new redirect_uri" do
expect(response).to redirect_to("https://example.com/a_new_route#access_token=#{@auth.token}")
end
end
context "with state param given" do
before do
get :new, params: {
useraddress: user.address,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com",
client_id: "https://example.com",
state: "foobar123"
}
end
it "redirects to the redirect_uri with token and state" do
expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}&state=foobar123")
end
end
end
context "no scope" do
before do
get :new, params: {
useraddress: user.address,
redirect_uri: "https://example.com",
client_id: "https://example.com",
state: "foobar123"
}
end
it "redirects to the redirect_uri with an error code" do
expect(response).to redirect_to("https://example.com#error=invalid_scope&state=foobar123")
end
end
context "empty scope" do
before do
get :new, params: {
useraddress: user.address,
scope: "",
redirect_uri: "https://example.com",
client_id: "https://example.com",
state: "foobar123"
}
end
it "redirects to the redirect_uri with an error code" do
expect(response).to redirect_to("https://example.com#error=invalid_scope&state=foobar123")
end
end
end
context "when user is not signed in" do
it "redirects to the signin page with username pre-filled" do
get :new, params: {
useraddress: user.address,
scope: "documents,photos",
redirect_uri: "https://example.com"
}
expect(response).to redirect_to(new_user_session_path(cn: user.cn, ou: user.ou))
end
end
describe "root access" do
before do
sign_in user
end
describe "full" do
before do
get :new, params: {
useraddress: user.address,
scope: "*:rw",
redirect_uri: "https://example.com",
client_id: "example.com"
}
end
it "sets the instance variables" do
expect(assigns["scopes"]).to eq([":rw"])
expect(assigns["root_access_requested"]).to be(true)
end
end
describe "read-only" do
before do
get :new, params: {
useraddress: user.address,
scope: "*:r",
redirect_uri: "https://example.com",
client_id: "example.com"
}
end
it "sets the instance variables" do
expect(assigns["scopes"]).to eq([":r"])
expect(assigns["root_access_requested"]).to be(true)
end
end
end
end
describe "POST /rs/oauth/:useraddress" do
context "when user is signed in" do
before do
sign_in user
end
after do
user.remote_storage_authorizations.destroy_all
end
context "when no valid token exists" do
before do
post :create, params: {
user_id: user.id,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com",
client_id: "example.com",
state: "foobar123",
expire_at: 1.day.from_now
}
@auth = user.reload.remote_storage_authorizations.first
end
it "creates a new token" do
expect(@auth.permissions).to eq(%w(documents photos contacts:rw videos:r tasks/work:r))
end
it "redirects to the redirect_uri" do
expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}&state=foobar123")
end
end
context "when token is expired" do
before do
@auth = user.remote_storage_authorizations.create!(
permissions: %w(documents),
client_id: "example.com",
redirect_uri: "https://example.com",
expire_at: 1.day.ago,
token: nil
)
post :create, params: {
user_id: user.id,
scope: "documents",
redirect_uri: "https://example.com",
client_id: "example.com",
state: "foobar123",
expire_at: 1.month.from_now
}
end
it "updates the token" do
expect(@auth.reload.token).not_to be_nil
end
end
context "root access with several scopes" do
before do
post :create, params: {
user_id: user.id,
scope: "*:rw contacts:r",
redirect_uri: "https://example.com",
client_id: "example.com",
expire_at: 1.month.from_now
}
@auth = user.reload.remote_storage_authorizations.first
end
it "removes all scopes except for the root permission" do
expect(@auth.permissions).to eq(%w(:rw))
end
end
context "no redirect_uri" do
before do
post :create, params: {
user_id: user.id,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
client_id: "example.com",
expire_at: 1.month.from_now
}
end
it "returns a 400" do
expect(response.response_code).to eq(400)
end
end
context "no client_id" do
before do
post :create, params: {
user_id: user.id,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
redirect_uri: "https://example.com",
expire_at: 1.month.from_now
}
end
it "redirects with invalid_request error" do
expect(response).to redirect_to("https://example.com#error=invalid_request")
end
end
context "hostnames of client_id and redirect_uri do not match" do
before do
post :create, params: {
user_id: user.id,
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
client_id: "fishing.com",
redirect_uri: "https://example.com",
expire_at: 1.month.from_now
}
end
it "redirects with invalid_client error" do
expect(response).to redirect_to("https://example.com#error=invalid_client")
end
end
context "empty scope" do
before do
post :create, params: {
user_id: user.id,
scope: "",
redirect_uri: "https://example.com",
client_id: "example.com",
state: "foobar123",
expire_at: 1.month.from_now
}
end
it "redirects to the redirect_uri with an error code" do
expect(response).to redirect_to("https://example.com#error=invalid_scope&state=foobar123")
end
end
context "when the user_id is different from the signed in user" do
before do
post :create, params: {
user_id: user.id,
scope: "documents,photos",
redirect_uri: "https://example.com",
client_id: "example.com",
expire_at: 1.month.from_now
}
end
it "returns a 403" do
post :create, params: {
user_id: "69",
scope: "documents,photos",
redirect_uri: "https://example.com",
client_id: "example.com",
expire_at: 1.month.from_now
}
expect(response.response_code).to eq(403)
end
end
end
context "when user is not signed in" do
it "redirects to the signin page" do
post :create, params: {
user_id: user.id,
scope: "documents,photos",
redirect_uri: "https://example.com",
client_id: "example.com",
expire_at: 1.month.from_now
}
expect(response).to redirect_to(new_user_session_path)
end
end
end
describe "GET /rs/oauth/token/:id/launch_app" do
context "when user is signed in" do
before do
sign_in user
end
context "token exists" do
before do
@auth = user.remote_storage_authorizations.create!(
permissions: %w(documents), client_id: "app.example.com",
redirect_uri: "https://app.example.com",
expire_at: 2.days.from_now
)
get :launch_app, params: { id: @auth.id }
end
after do
@auth.destroy
end
it "redirects to the given URL with the correct RS URL fragment params" do
launch_url = "https://app.example.com#remotestorage=#{user.address}&access_token=#{@auth.token}"
expect(response).to redirect_to(launch_url)
end
end
end
end
end

View File

@@ -0,0 +1,66 @@
require 'rails_helper'
RSpec.describe 'remoteStorage OAuth Dialog', type: :feature do
context "when signed in" do
let(:user) { create :user }
before do
login_as user, :scope => :user
end
context "with normal permissions" do
before do
visit new_rs_oauth_path(useraddress: user.address,
redirect_uri: "http://example.com",
client_id: "http://example.com",
scope: "documents,[photos], contacts:r")
end
it "shows the permissions in a list" do
within ".permissions" do
expect(page).to have_content("documents")
expect(page).to have_content("photos")
expect(page).to have_content("contacts")
end
within ".scope:first-of-type" do
expect(page).not_to have_content("read only")
end
within ".scope:last-of-type" do
expect(page).to have_content("read only")
end
end
end
context "root access" do
context "full" do
before do
visit new_rs_oauth_path(useraddress: user.address,
redirect_uri: "http://example.com",
client_id: "http://example.com",
scope: ":rw")
end
it "shows a special permission for all files and dirs" do
within ".scope" do
expect(page).to have_content("All files and directories")
end
end
end
end
end
context "when signed out" do
let(:user) { create :user }
it "prefills the username field in the signin form" do
visit new_rs_oauth_path(useraddress: user.address,
redirect_uri: "http://example.com",
client_id: "http://example.com",
scope: "documents,[photos], contacts:r")
expect(find("#user_cn").value).to eq(user.cn)
end
end
end

View File

@@ -25,7 +25,7 @@ RSpec.describe CreateLndhubAccountJob, type: :job do
context "with existing credentials stored" do
before do
user.ln_login = "foo"
user.ln_account = "foo"
user.ln_password = "bar"
user.save!
end
@@ -36,7 +36,7 @@ RSpec.describe CreateLndhubAccountJob, type: :job do
expect(WebMock).to_not have_requested(:post, "http://localhost:3023/create")
user.reload
expect(user.ln_login).to eq("foo")
expect(user.ln_account).to eq("foo")
expect(user.ln_password).to eq("bar")
end
end

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
RSpec.describe ExpireRemoteStorageAuthorizationJob, type: :job do
RSpec.describe RemoteStorageExpireAuthorizationJob, type: :job do
before do
@user = create :user, cn: "ronald", ou: "kosmos.org"
@rs_authorization = create :remote_storage_authorization, user: @user, expire_at: 1.day.ago
@@ -16,7 +16,7 @@ RSpec.describe ExpireRemoteStorageAuthorizationJob, type: :job do
}
let(:redis) {
@redis ||= Redis.new(url: Setting.redis_url)
@redis ||= Redis.new(url: Setting.rs_redis_url)
}
it "removes the RS authorization from redis" do

View File

@@ -12,6 +12,38 @@ RSpec.describe User, type: :model do
end
end
describe "#mastodon_address" do
let(:user) { build :user, cn: "jimmy", ou: "kosmos.org" }
context "Mastodon service not configured" do
it "returns nil" do
expect(user.mastodon_address).to be_nil
end
end
context "Mastodon service configured" do
before do
Setting.mastodon_enabled = true
end
describe "domain is the same as primary domain" do
it "returns the user address" do
expect(user.mastodon_address).to eq("jimmy@kosmos.org")
end
end
describe "domain is different from primary domain" do
before do
Setting.mastodon_address_domain = "kosmos.social"
end
it "returns the user address" do
expect(user.mastodon_address).to eq("jimmy@kosmos.social")
end
end
end
end
describe "#is_admin?" do
it "returns true when admin flag is set in LDAP" do
expect(Devise::LDAP::Adapter).to receive(:get_ldap_param)

File diff suppressed because one or more lines are too long