CVE-2023-48220: Insufficient Information (NVD-CWE-noinfo)
value: Decidim is a participatory democracy framework. Starting in version 0.4.rc3 and prior to version 2.0.9 of the `devise_invitable` gem, the invites feature allows users to accept the invitation for an unlimited amount of time through the password reset functionality. This issue creates vulnerable dependencies starting in version 0.0.1.alpha3 and prior to versions 0.26.9, 0.27.5, and 0.28.0 of the `decidim,` `decidim-admin`, and `decidim-system` gems. When using the password reset functionality, the `devise_invitable` gem always accepts the pending invitation if the user has been invited. The only check done is if the user has been invited but the code does not ensure that the pending invitation is still valid as defined by the `invite_for` expiry period. Decidim sets this configuration to `2.weeks` so this configuration should be respected. The bug is in the `devise_invitable` gem and should be fixed there and the dependency should be upgraded in Decidim once the fix becomes available. `devise_invitable` to version `2.0.9` and above fix this issue. Versions 0.26.9, 0.27.5, and 0.28.0 of the `decidim,` `decidim-admin`, and `decidim-system` gems contain this fix. As a workaround, invitations can be cancelled directly from the database.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-20T18:15Z
Repository: https://github.com/decidim/decidim
Language: Ruby
Files changed: Gemfile, Gemfile.lock, Rakefile, decidim-admin/Gemfile, decidim-admin/Gemfile.lock, decidim-admin/README.md, decidim-admin/Rakefile, decidim-admin/app/assets/config/decidim_admin_manifest.js, decidim-admin/app/assets/javascripts/decidim/admin.js, decidim-admin/app/assets/stylesheets/decidim/admin.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_actions.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_foundation_and_overrides.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_layout.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_login.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_settings.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_sidebar.scss, decidim-admin/app/assets/stylesheets/decidim/admin/_tables.scss, decidim-admin/app/constraints/admin/organization_dashboard_constraint.rb, decidim-admin/app/controllers/decidim/admin/application_controller.rb, decidim-admin/app/controllers/decidim/admin/dashboard_controller.rb, decidim-admin/app/helpers/decidim/admin/application_helper.rb, decidim-admin/app/jobs/decidim/admin/application_job.rb, decidim-admin/app/mailers/decidim/admin/application_mailer.rb, decidim-admin/app/models/decidim/admin/application_record.rb, decidim-admin/app/views/decidim/admin/dashboard/show.html.erb, decidim-admin/app/views/decidim/admin/devise/mailers/password_change.html.erb, decidim-admin/app/views/decidim/admin/devise/mailers/reset_password_instructions.html.erb, decidim-admin/app/views/layouts/decidim/admin/_header.html.erb, decidim-admin/app/views/layouts/decidim/admin/_login_items.html.erb, decidim-admin/app/views/layouts/decidim/admin/_sidebar.html.erb, decidim-admin/app/views/layouts/decidim/admin/application.html.erb, decidim-admin/app/views/layouts/decidim/admin/login.html.erb, decidim-admin/bin/rails, decidim-admin/config/i18n-tasks.yml, decidim-admin/config/locales/en.yml, decidim-admin/config/routes.rb, decidim-admin/decidim-admin.gemspec, decidim-admin/lib/decidim/admin.rb, decidim-admin/lib/decidim/admin/engine.rb, decidim-admin/lib/generators/decidim/admin/dummy_generator.rb, decidim-admin/lib/tasks/decidim/admin_tasks.rake, decidim-admin/spec/factories.rb, decidim-admin/spec/features/admin_access_control.rb, decidim-admin/spec/features/admin_invite_spec.rb, decidim-admin/spec/i18n_spec.rb, decidim-admin/spec/spec_helper.rb, decidim-admin/spec/support/action_mailer.rb, decidim-admin/spec/support/capybara.rb, decidim-admin/spec/support/database_cleaner.rb, decidim-admin/spec/support/factory_girl.rb, decidim-admin/spec/support/warden.rb, decidim-admin/spec/support/wisper.rb, decidim-core/Gemfile, decidim-core/Gemfile.lock, decidim-core/app/controllers/decidim/devise/invitations_controller.rb, decidim-core/app/controllers/decidim/devise/passwords_controller.rb, decidim-core/app/controllers/decidim/devise/sessions_controller.rb, decidim-core/app/mailers/decidim/decidim_devise_mailer.rb, decidim-core/app/middleware/decidim/current_organization.rb, decidim-core/app/models/decidim/user.rb, decidim-core/app/views/decidim/devise/invitations/edit.html.erb, decidim-core/app/views/decidim/devise/passwords/edit.html.erb, decidim-core/app/views/decidim/devise/passwords/new.html.erb, decidim-core/app/views/decidim/devise/sessions/new.html.erb, decidim-core/app/views/decidim/devise/shared/_links.html.erb, decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.html.erb, decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.text.erb, decidim-core/config/initializers/devise.rb, decidim-core/lib/decidim/core/engine.rb, decidim-core/lib/devise/models/decidim_validatable.rb, decidim-core/lib/generators/decidim/install_generator.rb, decidim-core/spec/middleware/decidim/current_organization_spec.rb, decidim-core/spec/models/decidim/user_spec.rb, decidim-system/Gemfile.lock, decidim-system/app/commands/decidim/system/register_organization.rb, decidim-system/app/helpers/decidim/system/application_helper.rb, decidim-system/spec/commands/decidim/system/register_organization_spec.rb, decidim-system/spec/support/action_mailer.rb, decidim.gemspec, lib/decidim.rb
Commit date: 2016-09-27 16:49:41 +0200
Commit message: Add Organization admin engine

Diff: diff --git a/Gemfile b/Gemfile
index 2a1ec2d48..0bd198502 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,7 +6,9 @@ gemspec
 gem "rspec_junit_formatter", "0.2.3", group: :test, require: false
 gem "codecov", require: false, group: :test
 
 gemspec path: "."
 gemspec path: "decidim-core"
+gemspec path: "decidim-system"
+gemspec path: "decidim-admin"
 
 gem "rubocop"
diff --git a/Gemfile.lock b/Gemfile.lock
index dbe5cdbc1..03608ffb9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -11,14 +11,19 @@ GIT
 
 PATH
   remote: .
   specs:
     decidim (0.0.1.alpha2)
+      decidim-admin (= 0.0.1.alpha2)
       decidim-core (= 0.0.1.alpha2)
       decidim-system (= 0.0.1.alpha2)
       rails (~> 5.0.0)
-    decidim-system (0.0.1.alpha2)
+
+PATH
+  remote: decidim-admin
+  specs:
+    decidim-admin (0.0.1.alpha2)
       active_link_to (~> 1.0.0)
       decidim-core
       devise (~> 4.2)
       devise_invitable (~> 1.7.0)
       foundation-rails (~> 6.2.3.0)
@@ -45,10 +50,28 @@ PATH
       rails (~> 5.0.0)
       rectify (~> 0.6)
       sass-rails (~> 5.0.0)
       turbolinks (~> 5.0.0)
 
+PATH
+  remote: decidim-system
+  specs:
+    decidim-system (0.0.1.alpha2)
+      active_link_to (~> 1.0.0)
+      decidim-core
+      devise (~> 4.2)
+      devise_invitable (~> 1.7.0)
+      foundation-rails (~> 6.2.3.0)
+      foundation_rails_helper (~> 2.0.0)
+      jbuilder (~> 2.5)
+      jquery-rails (~> 4.0)
+      jquery-turbolinks (~> 2.1.0)
+      rails (~> 5.0.0)
+      rectify (~> 0.6)
+      sass-rails (~> 5.0.0)
+      turbolinks (~> 5.0.0)
+
 GEM
   remote: https://rubygems.org/
   specs:
     actioncable (5.0.0.1)
       actionpack (= 5.0.0.1)
@@ -172,10 +195,12 @@ GEM
       thor (>= 0.14, < 2.0)
     jquery-turbolinks (2.1.0)
       railties (>= 3.1.0)
       turbolinks
     json (2.0.2)
+    launchy (2.4.3)
+      addressable (~> 2.3)
     listen (3.0.8)
       rb-fsevent (~> 0.9, >= 0.9.4)
       rb-inotify (~> 0.9, >= 0.9.7)
     loofah (2.0.3)
       nokogiri (>= 1.5.9)
@@ -330,14 +355,17 @@ DEPENDENCIES
   byebug
   capybara (~> 2.4)
   codecov
   database_cleaner (~> 1.5.0)
   decidim!
+  decidim-admin!
   decidim-core!
+  decidim-system!
   factory_girl_rails
   foundation_rails_helper!
   i18n-tasks (~> 0.9.5)
+  launchy
   listen
   pg
   rake (~> 11.0)
   rspec (~> 3.0)
   rspec-rails (~> 3.5)
diff --git a/Rakefile b/Rakefile
index b8e469823..636d825f3 100644
--- a/Rakefile
+++ b/Rakefile
@@ -6,11 +6,11 @@ begin
   require "decidim/testing_support/common_rake"
 rescue LoadError
   raise "Could not find decidim/testing_support/common_rake. You need to run this command using Bundler."
 end
 
-DECIDIM_GEMS = %w(core system).freeze
+DECIDIM_GEMS = %w(core system admin).freeze
 
 RSpec::Core::RakeTask.new(:spec)
 
 task default: :test
 
diff --git a/decidim-admin/Gemfile b/decidim-admin/Gemfile
new file mode 100644
index 000000000..bcdf3a706
--- /dev/null
+++ b/decidim-admin/Gemfile
@@ -0,0 +1,17 @@
+eval(File.read(File.dirname(__FILE__) + "/../common_gemfile.rb"))
+
+# Declare your gem's dependencies in decidim-admin.gemspec.
+# Bundler will treat runtime dependencies like base dependencies, and
+# development dependencies will be added by default to the :development group.
+gemspec
+
+# Declare any dependencies that are still in development here instead of in
+# your gemspec. These might include edge Rails or gems from your path or
+# Git. Remember to move these dependencies to your gemspec before releasing
+# your gem to rubygems.org.
+
+# To use a debugger
+# gem 'byebug', group: [:development, :test]
+
+gemspec path: "../decidim-core"
+gemspec path: ".."
diff --git a/decidim-admin/Gemfile.lock b/decidim-admin/Gemfile.lock
new file mode 100644
index 000000000..e4f697a03
--- /dev/null
+++ b/decidim-admin/Gemfile.lock
@@ -0,0 +1,351 @@
+GIT
+  remote: https://github.com/sgruhier/foundation_rails_helper
+  revision: d8819b3a5f9d82251aac7559e0d07505c9777717
+  specs:
+    foundation_rails_helper (2.0.0)
+      actionpack (>= 4.1)
+      activemodel (>= 4.1)
+      activesupport (>= 4.1)
+      railties (>= 4.1)
+      tzinfo (~> 1.2, >= 1.2.2)
+
+PATH
+  remote: ../decidim-core
+  specs:
+    decidim-core (0.0.1.alpha2)
+      active_link_to (~> 1.0.0)
+      devise (~> 4.2)
+      foundation-rails (~> 6.2.3.0)
+      foundation_rails_helper (~> 2.0.0)
+      jbuilder (~> 2.5)
+      jquery-rails (~> 4.0)
+      jquery-turbolinks (~> 2.1.0)
+      rails (~> 5.0.0)
+      rectify (~> 0.6)
+      sass-rails (~> 5.0.0)
+      turbolinks (~> 5.0.0)
+
+PATH
+  remote: ..
+  specs:
+    decidim (0.0.1.alpha2)
+      decidim-admin (= 0.0.1.alpha2)
+      decidim-core (= 0.0.1.alpha2)
+      decidim-system (= 0.0.1.alpha2)
+      rails (~> 5.0.0)
+    decidim-system (0.0.1.alpha2)
+      active_link_to (~> 1.0.0)
+      decidim-core
+      devise (~> 4.2)
+      devise_invitable (~> 1.7.0)
+      foundation-rails (~> 6.2.3.0)
+      foundation_rails_helper (~> 2.0.0)
+      jbuilder (~> 2.5)
+      jquery-rails (~> 4.0)
+      jquery-turbolinks (~> 2.1.0)
+      rails (~> 5.0.0)
+      rectify (~> 0.6)
+      sass-rails (~> 5.0.0)
+      turbolinks (~> 5.0.0)
+
+PATH
+  remote: .
+  specs:
+    decidim-admin (0.0.1.alpha2)
+      active_link_to (~> 1.0.0)
+      decidim-core
+      devise (~> 4.2)
+      devise_invitable (~> 1.7.0)
+      foundation-rails (~> 6.2.3.0)
+      foundation_rails_helper (~> 2.0.0)
+      jbuilder (~> 2.5)
+      jquery-rails (~> 4.0)
+      jquery-turbolinks (~> 2.1.0)
+      rails (~> 5.0.0)
+      rectify (~> 0.6)
+      sass-rails (~> 5.0.0)
+      turbolinks (~> 5.0.0)
+
+GEM
+  remote: https://rubygems.org/
+  specs:
+    actioncable (5.0.0.1)
+      actionpack (= 5.0.0.1)
+      nio4r (~> 1.2)
+      websocket-driver (~> 0.6.1)
+    actionmailer (5.0.0.1)
+      actionpack (= 5.0.0.1)
+      actionview (= 5.0.0.1)
+      activejob (= 5.0.0.1)
+      mail (~> 2.5, >= 2.5.4)
+      rails-dom-testing (~> 2.0)
+    actionpack (5.0.0.1)
+      actionview (= 5.0.0.1)
+      activesupport (= 5.0.0.1)
+      rack (~> 2.0)
+      rack-test (~> 0.6.3)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    actionview (5.0.0.1)
+      activesupport (= 5.0.0.1)
+      builder (~> 3.1)
+      erubis (~> 2.7.0)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    active_link_to (1.0.3)
+      actionpack
+    activejob (5.0.0.1)
+      activesupport (= 5.0.0.1)
+      globalid (>= 0.3.6)
+    activemodel (5.0.0.1)
+      activesupport (= 5.0.0.1)
+    activerecord (5.0.0.1)
+      activemodel (= 5.0.0.1)
+      activesupport (= 5.0.0.1)
+      arel (~> 7.0)
+    activesupport (5.0.0.1)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (~> 0.7)
+      minitest (~> 5.1)
+      tzinfo (~> 1.1)
+    addressable (2.4.0)
+    arel (7.1.2)
+    ast (2.3.0)
+    axiom-types (0.1.1)
+      descendants_tracker (~> 0.0.4)
+      ice_nine (~> 0.11.0)
+      thread_safe (~> 0.3, >= 0.3.1)
+    babel-source (5.8.35)
+    babel-transpiler (0.7.0)
+      babel-source (>= 4.0, < 6)
+      execjs (~> 2.0)
+    bcrypt (3.1.11)
+    builder (3.2.2)
+    byebug (9.0.5)
+    capybara (2.9.1)
+      addressable
+      mime-types (>= 1.16)
+      nokogiri (>= 1.3.3)
+      rack (>= 1.0.0)
+      rack-test (>= 0.5.4)
+      xpath (~> 2.0)
+    coercible (1.0.0)
+      descendants_tracker (~> 0.0.1)
+    concurrent-ruby (1.0.2)
+    database_cleaner (1.5.3)
+    descendants_tracker (0.0.4)
+      thread_safe (~> 0.3, >= 0.3.1)
+    devise (4.2.0)
+      bcrypt (~> 3.0)
+      orm_adapter (~> 0.1)
+      railties (>= 4.1.0, < 5.1)
+      responders
+      warden (~> 1.2.3)
+    devise_invitable (1.7.0)
+      actionmailer (>= 4.0.0)
+      devise (>= 4.0.0)
+    diff-lcs (1.2.5)
+    easy_translate (0.5.0)
+      json
+      thread
+      thread_safe
+    equalizer (0.0.11)
+    erubis (2.7.0)
+    execjs (2.7.0)
+    factory_girl (4.7.0)
+      activesupport (>= 3.0.0)
+    factory_girl_rails (4.7.0)
+      factory_girl (~> 4.7.0)
+      railties (>= 3.0.0)
+    ffi (1.9.14)
+    foundation-rails (6.2.3.0)
+      railties (>= 3.1.0)
+      sass (>= 3.3.0, < 3.5)
+      sprockets-es6 (>= 0.9.0)
+    globalid (0.3.7)
+      activesupport (>= 4.1.0)
+    highline (1.7.8)
+    i18n (0.7.0)
+    i18n-tasks (0.9.5)
+      activesupport (>= 4.0.2)
+      ast (>= 2.1.0)
+      easy_translate (>= 0.5.0)
+      erubis
+      highline (>= 1.7.3)
+      i18n
+      parser (>= 2.2.3.0)
+      term-ansicolor (>= 1.3.2)
+      terminal-table (>= 1.5.1)
+    ice_nine (0.11.2)
+    jbuilder (2.6.0)
+      activesupport (>= 3.0.0, < 5.1)
+      multi_json (~> 1.2)
+    jquery-rails (4.2.1)
+      rails-dom-testing (>= 1, < 3)
+      railties (>= 4.2.0)
+      thor (>= 0.14, < 2.0)
+    jquery-turbolinks (2.1.0)
+      railties (>= 3.1.0)
+      turbolinks
+    json (2.0.2)
+    launchy (2.4.3)
+      addressable (~> 2.3)
+    listen (3.1.5)
+      rb-fsevent (~> 0.9, >= 0.9.4)
+      rb-inotify (~> 0.9, >= 0.9.7)
+      ruby_dep (~> 1.2)
+    loofah (2.0.3)
+      nokogiri (>= 1.5.9)
+    mail (2.6.4)
+      mime-types (>= 1.16, < 4)
+    method_source (0.8.2)
+    mime-types (3.1)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2016.0521)
+    mini_portile2 (2.1.0)
+    minitest (5.9.1)
+    multi_json (1.12.1)
+    nio4r (1.2.1)
+    nokogiri (1.6.8)
+      mini_portile2 (~> 2.1.0)
+      pkg-config (~> 1.1.7)
+    orm_adapter (0.5.0)
+    parser (2.3.1.4)
+      ast (~> 2.2)
+    pg (0.19.0)
+    pkg-config (1.1.7)
+    rack (2.0.1)
+    rack-test (0.6.3)
+      rack (>= 1.0)
+    rails (5.0.0.1)
+      actioncable (= 5.0.0.1)
+      actionmailer (= 5.0.0.1)
+      actionpack (= 5.0.0.1)
+      actionview (= 5.0.0.1)
+      activejob (= 5.0.0.1)
+      activemodel (= 5.0.0.1)
+      activerecord (= 5.0.0.1)
+      activesupport (= 5.0.0.1)
+      bundler (>= 1.3.0, < 2.0)
+      railties (= 5.0.0.1)
+      sprockets-rails (>= 2.0.0)
+    rails-dom-testing (2.0.1)
+      activesupport (>= 4.2.0, < 6.0)
+      nokogiri (~> 1.6.0)
+    rails-html-sanitizer (1.0.3)
+      loofah (~> 2.0)
+    railties (5.0.0.1)
+      actionpack (= 5.0.0.1)
+      activesupport (= 5.0.0.1)
+      method_source
+      rake (>= 0.8.7)
+      thor (>= 0.18.1, < 2.0)
+    rake (11.3.0)
+    rb-fsevent (0.9.7)
+    rb-inotify (0.9.7)
+      ffi (>= 0.5.0)
+    rectify (0.6.1)
+      activemodel (>= 4.1.0)
+      activerecord (>= 4.1.0)
+      activesupport (>= 4.1.0)
+      virtus (~> 1.0.5)
+      wisper (>= 1.6.1)
+    responders (2.3.0)
+      railties (>= 4.2.0, < 5.1)
+    rspec (3.5.0)
+      rspec-core (~> 3.5.0)
+      rspec-expectations (~> 3.5.0)
+      rspec-mocks (~> 3.5.0)
+    rspec-core (3.5.3)
+      rspec-support (~> 3.5.0)
+    rspec-expectations (3.5.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.5.0)
+    rspec-mocks (3.5.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.5.0)
+    rspec-rails (3.5.2)
+      actionpack (>= 3.0)
+      activesupport (>= 3.0)
+      railties (>= 3.0)
+      rspec-core (~> 3.5.0)
+      rspec-expectations (~> 3.5.0)
+      rspec-mocks (~> 3.5.0)
+      rspec-support (~> 3.5.0)
+    rspec-support (3.5.0)
+    ruby_dep (1.4.0)
+    sass (3.4.22)
+    sass-rails (5.0.6)
+      railties (>= 4.0.0, < 6)
+      sass (~> 3.1)
+      sprockets (>= 2.8, < 4.0)
+      sprockets-rails (>= 2.0, < 4.0)
+      tilt (>= 1.1, < 3)
+    sprockets (3.7.0)
+      concurrent-ruby (~> 1.0)
+      rack (> 1, < 3)
+    sprockets-es6 (0.9.2)
+      babel-source (>= 5.8.11)
+      babel-transpiler
+      sprockets (>= 3.0.0)
+    sprockets-rails (3.2.0)
+      actionpack (>= 4.0)
+      activesupport (>= 4.0)
+      sprockets (>= 3.0.0)
+    term-ansicolor (1.3.2)
+      tins (~> 1.0)
+    terminal-table (1.7.3)
+      unicode-display_width (~> 1.1.1)
+    thor (0.19.1)
+    thread (0.2.2)
+    thread_safe (0.3.5)
+    tilt (2.0.5)
+    tins (1.12.0)
+    turbolinks (5.0.1)
+      turbolinks-source (~> 5)
+    turbolinks-source (5.0.0)
+    tzinfo (1.2.2)
+      thread_safe (~> 0.1)
+    unicode-display_width (1.1.1)
+    virtus (1.0.5)
+      axiom-types (~> 0.1)
+      coercible (~> 1.0)
+      descendants_tracker (~> 0.0, >= 0.0.3)
+      equalizer (~> 0.0, >= 0.0.9)
+    warden (1.2.6)
+      rack (>= 1.0)
+    websocket-driver (0.6.4)
+      websocket-extensions (>= 0.1.0)
+    websocket-extensions (0.1.2)
+    wisper (1.6.1)
+    wisper-rspec (0.0.2)
+    xpath (2.0.0)
+      nokogiri (~> 1.3)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  bundler (~> 1.12)
+  byebug
+  capybara (~> 2.4)
+  database_cleaner (~> 1.5.0)
+  decidim!
+  decidim-admin!
+  decidim-core!
+  factory_girl_rails
+  foundation_rails_helper!
+  i18n-tasks (~> 0.9.5)
+  launchy
+  listen
+  pg
+  rake (~> 11.0)
+  rspec (~> 3.0)
+  rspec-rails (~> 3.5)
+  wisper-rspec
+
+RUBY VERSION
+   ruby 2.3.1p112
+
+BUNDLED WITH
+   1.13.1
diff --git a/decidim-admin/README.md b/decidim-admin/README.md
new file mode 100644
index 000000000..f9451e821
--- /dev/null
+++ b/decidim-admin/README.md
@@ -0,0 +1,28 @@
+# Decidim::Admin
+Short description and motivation.
+
+## Usage
+How to use my plugin.
+
+## Installation
+Add this line to your application's Gemfile:
+
+```ruby
+gem 'decidim-admin'
+```
+
+And then execute:
+```bash
+$ bundle
+```
+
+Or install it yourself as:
+```bash
+$ gem install decidim-admin
+```
+
+## Contributing
+Contribution directions go here.
+
+## License
+The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
diff --git a/decidim-admin/Rakefile b/decidim-admin/Rakefile
new file mode 100644
index 000000000..9df38b147
--- /dev/null
+++ b/decidim-admin/Rakefile
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+begin
+  require "bundler/setup"
+rescue LoadError
+  puts "You must `gem install bundler` and `bundle install` to run rake tasks"
+end
+
+require_relative "../decidim-core/lib/decidim/testing_support/common_rake"
+require "rdoc/task"
+
+RDoc::Task.new(:rdoc) do |rdoc|
+  rdoc.rdoc_dir = "rdoc"
+  rdoc.title    = "Decidim::Admin"
+  rdoc.options << "--line-numbers"
+  rdoc.rdoc_files.include("README.md")
+  rdoc.rdoc_files.include("lib/**/*.rb")
+end
+
+load "rails/tasks/statistics.rake"
+
+require "bundler/gem_tasks"
+
+task default: :test
+
+desc "Generates a dummy app for testing"
+task :test_app do
+  ENV["LIB_NAME"] = "decidim/admin"
+  Rake::Task["common:test_app"].invoke
+end
+
+task test: :test_app do
+  Dir.chdir(File.dirname(__FILE__).to_s) do
+    sh "rspec spec"
+  end
+end
diff --git a/decidim-admin/app/assets/config/decidim_admin_manifest.js b/decidim-admin/app/assets/config/decidim_admin_manifest.js
new file mode 100644
index 000000000..410eb2f49
--- /dev/null
+++ b/decidim-admin/app/assets/config/decidim_admin_manifest.js
@@ -0,0 +1,2 @@
+//= link_directory ../javascripts/decidim/admin.js
+//= link_directory ../stylesheets/decidim/admin.css
diff --git a/decidim-admin/app/assets/images/decidim/admin/.keep b/decidim-admin/app/assets/images/decidim/admin/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/decidim-admin/app/assets/javascripts/decidim/admin.js b/decidim-admin/app/assets/javascripts/decidim/admin.js
new file mode 100644
index 000000000..d102e795f
--- /dev/null
+++ b/decidim-admin/app/assets/javascripts/decidim/admin.js
@@ -0,0 +1,21 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
+//
+// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
+// about supported directives.
+//
+//= require jquery
+//= require jquery.turbolinks
+//= require jquery_ujs
+//= require foundation
+//= require_self
+
+$(function(){ $(document).foundation(); });
+
+//= require turbolinks
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin.scss b/decidim-admin/app/assets/stylesheets/decidim/admin.scss
new file mode 100644
index 000000000..d66b29ec6
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin.scss
@@ -0,0 +1,8 @@
+@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';
+
+@import "admin/foundation_and_overrides";
+@import "admin/layout";
+@import "admin/login";
+@import "admin/sidebar";
+@import "admin/tables";
+@import "admin/actions";
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_actions.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_actions.scss
new file mode 100644
index 000000000..4e6025036
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_actions.scss
@@ -0,0 +1,8 @@
+.actions{
+  text-align: right;
+
+  &.title{
+    margin-bottom: $global-margin;
+    font-size: 1.2em;
+  }
+}
\ No newline at end of file
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_foundation_and_overrides.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_foundation_and_overrides.scss
new file mode 100644
index 000000000..3aff02e8c
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_foundation_and_overrides.scss
@@ -0,0 +1,52 @@
+@charset "utf-8";
+
+@import "settings";
+@import "foundation";
+
+// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
+//
+// @import 'motion-ui/motion-ui';
+
+// We include everything by default.  To slim your CSS, remove components you don't use.
+
+@include foundation-global-styles;
+@include foundation-flex-grid;
+@include foundation-typography;
+@include foundation-button;
+@include foundation-forms;
+@include foundation-visibility-classes;
+@include foundation-flex-classes;
+@include foundation-accordion;
+@include foundation-accordion-menu;
+@include foundation-badge;
+@include foundation-breadcrumbs;
+@include foundation-button-group;
+@include foundation-callout;
+@include foundation-close-button;
+@include foundation-drilldown-menu;
+@include foundation-dropdown;
+@include foundation-dropdown-menu;
+@include foundation-flex-video;
+@include foundation-label;
+@include foundation-media-object;
+@include foundation-menu;
+@include foundation-menu-icon;
+@include foundation-off-canvas;
+@include foundation-orbit;
+@include foundation-pagination;
+@include foundation-progress-bar;
+@include foundation-slider;
+@include foundation-sticky;
+@include foundation-reveal;
+@include foundation-switch;
+@include foundation-table;
+@include foundation-tabs;
+@include foundation-thumbnail;
+@include foundation-title-bar;
+@include foundation-tooltip;
+@include foundation-top-bar;
+
+// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
+//
+// @include motion-ui-transitions;
+// @include motion-ui-animations;
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_layout.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_layout.scss
new file mode 100644
index 000000000..ef05a04d4
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_layout.scss
@@ -0,0 +1,16 @@
+.off-canvas, .sidebar, .off-canvas-wrapper{
+  min-height: 100vh;
+}
+
+.off-canvas{
+  color: #fff;
+}
+
+.main-content{
+  padding: $global-padding 0;
+  min-height: 100vh;
+}
+
+.page-title{
+  margin-bottom: $global-margin;
+}
\ No newline at end of file
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_login.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_login.scss
new file mode 100644
index 000000000..c9e389221
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_login.scss
@@ -0,0 +1,36 @@
+body.login{
+  background: $light-gray;
+  position: relative;
+
+  .login-form-wrapper{
+    @include grid-row;
+    @include flex;
+
+    height: 100vh;
+
+    .login-form{
+      @include grid-column(12);
+      @include grid-column-position(center);
+
+      @include breakpoint(medium){
+        @include grid-column(8);
+        @include grid-column-position(center);
+      }
+
+      @include breakpoint(large){
+        @include grid-column(6);
+        @include grid-column-position(center);
+      }
+    }
+
+    @include breakpoint(medium){
+      @include flex-align(center, middle);
+      transform: translateY(-8%);
+    }
+
+    .login-form-inner{
+      background-color: $white;
+      padding: $global-padding;
+    }
+  }
+}
\ No newline at end of file
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_settings.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_settings.scss
new file mode 100644
index 000000000..e73db74a2
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_settings.scss
@@ -0,0 +1,566 @@
+//  Foundation for Sites Settings
+//  -----------------------------
+//
+//  Table of Contents:
+//
+//   1. Global
+//   2. Breakpoints
+//   3. The Grid
+//   4. Base Typography
+//   5. Typography Helpers
+//   6. Abide
+//   7. Accordion
+//   8. Accordion Menu
+//   9. Badge
+//  10. Breadcrumbs
+//  11. Button
+//  12. Button Group
+//  13. Callout
+//  14. Close Button
+//  15. Drilldown
+//  16. Dropdown
+//  17. Dropdown Menu
+//  18. Flex Video
+//  19. Forms
+//  20. Label
+//  21. Media Object
+//  22. Menu
+//  23. Meter
+//  24. Off-canvas
+//  25. Orbit
+//  26. Pagination
+//  27. Progress Bar
+//  28. Reveal
+//  29. Slider
+//  30. Switch
+//  31. Table
+//  32. Tabs
+//  33. Thumbnail
+//  34. Title Bar
+//  35. Tooltip
+//  36. Top Bar
+
+@import "util/util";
+
+// 1. Global
+// ---------
+
+// $global-font-size: 100%;
+// $global-width: rem-calc(1200);
+// $global-lineheight: 1.5;
+$foundation-palette: (
+  primary: #2199e8,
+  secondary: #777,
+  success: #3adb76,
+  warning: #ffae00,
+  alert: #ec5840,
+);
+$light-gray: #e6e6e6;
+// $medium-gray: #cacaca;
+// $dark-gray: #8a8a8a;
+$black: #232323;
+// $white: #fefefe;
+// $body-background: $white;
+// $body-font-color: $black;
+$body-font-family: 'Montserrat', sans-serif;
+// $body-antialiased: true;
+// $global-margin: 1rem;
+// $global-padding: 1rem;
+// $global-weight-normal: normal;
+// $global-weight-bold: bold;
+// $global-radius: 0;
+// $global-text-direction: ltr;
+$global-flexbox: true;
+// $print-transparent-backgrounds: true;
+
+@include add-foundation-colors;
+
+// 2. Breakpoints
+// --------------
+
+// $breakpoints: (
+//   small: 0,
+//   medium: 640px,
+//   large: 1024px,
+//   xlarge: 1200px,
+//   xxlarge: 1440px,
+// );
+// $breakpoint-classes: (small medium large);
+
+// 3. The Grid
+// -----------
+
+// $grid-row-width: $global-width;
+// $grid-column-count: 12;
+// $grid-column-gutter: (
+//   small: 20px,
+//   medium: 30px,
+// );
+// $grid-column-align-edge: true;
+// $block-grid-max: 8;
+
+// 4. Base Typography
+// ------------------
+
+// $header-font-family: $body-font-family;
+// $header-font-weight: $global-weight-normal;
+// $header-font-style: normal;
+// $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
+// $header-sizes: (
+//   small: (
+//     'h1': 24,
+//     'h2': 20,
+//     'h3': 19,
+//     'h4': 18,
+//     'h5': 17,
+//     'h6': 16,
+//   ),
+//   medium: (
+//     'h1': 48,
+//     'h2': 40,
+//     'h3': 31,
+//     'h4': 25,
+//     'h5': 20,
+//     'h6': 16,
+//   ),
+// );
+// $header-color: inherit;
+// $header-lineheight: 1.4;
+// $header-margin-bottom: 0.5rem;
+// $header-text-rendering: optimizeLegibility;
+// $small-font-size: 80%;
+// $header-small-font-color: $medium-gray;
+// $paragraph-lineheight: 1.6;
+// $paragraph-margin-bottom: 1rem;
+// $paragraph-text-rendering: optimizeLegibility;
+// $code-color: $black;
+// $code-font-family: $font-family-monospace;
+// $code-font-weight: $global-weight-normal;
+// $code-background: $light-gray;
+// $code-border: 1px solid $medium-gray;
+// $code-padding: rem-calc(2 5 1);
+// $anchor-color: $primary-color;
+// $anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
+// $anchor-text-decoration: none;
+// $anchor-text-decoration-hover: none;
+// $hr-width: $global-width;
+// $hr-border: 1px solid $medium-gray;
+// $hr-margin: rem-calc(20) auto;
+// $list-lineheight: $paragraph-lineheight;
+// $list-margin-bottom: $paragraph-margin-bottom;
+// $list-style-type: disc;
+// $list-style-position: outside;
+// $list-side-margin: 1.25rem;
+// $list-nested-side-margin: 1.25rem;
+// $defnlist-margin-bottom: 1rem;
+// $defnlist-term-weight: $global-weight-bold;
+// $defnlist-term-margin-bottom: 0.3rem;
+// $blockquote-color: $dark-gray;
+// $blockquote-padding: rem-calc(9 20 0 19);
+// $blockquote-border: 1px solid $medium-gray;
+// $cite-font-size: rem-calc(13);
+// $cite-color: $dark-gray;
+// $keystroke-font: $font-family-monospace;
+// $keystroke-color: $black;
+// $keystroke-background: $light-gray;
+// $keystroke-padding: rem-calc(2 4 0);
+// $keystroke-radius: $global-radius;
+// $abbr-underline: 1px dotted $black;
+
+// 5. Typography Helpers
+// ---------------------
+
+// $lead-font-size: $global-font-size * 1.25;
+// $lead-lineheight: 1.6;
+// $subheader-lineheight: 1.4;
+// $subheader-color: $dark-gray;
+// $subheader-font-weight: $global-weight-normal;
+// $subheader-margin-t#23262Cop: 0.2rem;
+// $subheader-margin-bottom: 0.5rem;
+// $stat-font-size: 2.5rem;
+
+// 6. Abide
+// --------
+
+// $abide-inputs: true;
+// $abide-labels: true;
+// $input-background-invalid: map-get($foundation-palette, alert);
+// $form-label-color-invalid: map-get($foundation-palette, alert);
+// $input-error-color: map-get($foundation-palette, alert);
+// $input-error-font-size: rem-calc(12);
+// $input-error-font-weight: $global-weight-bold;
+
+// 7. Accordion
+// ------------
+
+// $accordion-background: $white;
+// $accordion-plusminus: true;
+// $accordion-item-color: foreground($accordion-background, $primary-color);
+// $accordion-item-background-hover: $light-gray;
+// $accordion-item-padding: 1.25rem 1rem;
+// $accordion-content-background: $white;
+// $accordion-content-border: 1px solid $light-gray;
+// $accordion-content-color: foreground($accordion-content-background, $body-font-color);
+// $accordion-content-padding: 1rem;
+
+// 8. Accordion Menu
+// -----------------
+
+// $accordionmenu-arrows: true;
+// $accordionmenu-arrow-color: $primary-color;
+
+// 9. Badge
+// --------
+
+// $badge-background: $primary-color;
+// $badge-color: foreground($badge-background);
+// $badge-padding: 0.3em;
+// $badge-minwidth: 2.1em;
+// $badge-font-size: 0.6rem;
+
+// 10. Breadcrumbs
+// ---------------
+
+// $breadcrumbs-margin: 0 0 $global-margin 0;
+// $breadcrumbs-item-font-size: rem-calc(11);
+// $breadcrumbs-item-color: $primary-color;
+// $breadcrumbs-item-color-current: $black;
+// $breadcrumbs-item-color-disabled: $medium-gray;
+// $breadcrumbs-item-margin: 0.75rem;
+// $breadcrumbs-item-uppercase: true;
+// $breadcrumbs-item-slash: true;
+
+// 11. Button
+// ----------
+
+// $button-padding: 0.85em 1em;
+$button-margin: 0;
+// $button-fill: solid;
+// $button-background: $primary-color;
+// $button-background-hover: scale-color($button-background, $lightness: -15%);
+// $button-color: $white;
+// $button-color-alt: $black;
+// $button-radius: $global-radius;
+// $button-sizes: (
+//   tiny: 0.6rem,
+//   small: 0.75rem,
+//   default: 0.9rem,
+//   large: 1.25rem,
+// );
+// $button-opacity-disabled: 0.25;
+
+// 12. Button Group
+// ----------------
+
+// $buttongroup-margin: 1rem;
+// $buttongroup-spacing: 1px;
+// $buttongroup-child-selector: ".button";
+// $buttongroup-expand-max: 6;
+
+// 13. Callout
+// -----------
+
+// $callout-background: $white;
+// $callout-background-fade: 85%;
+// $callout-border: 1px solid rgba($black, 0.25);
+// $callout-margin: 0 0 1rem 0;
+// $callout-padding: 1rem;
+// $callout-font-color: $body-font-color;
+// $callout-font-color-alt: $body-background;
+// $callout-radius: $global-radius;
+// $callout-link-tint: 30%;
+
+// 14. Close Button
+// ----------------
+
+// $closebutton-position: right top;
+// $closebutton-offset-horizontal: 1rem;
+// $closebutton-offset-vertical: 0.5rem;
+// $closebutton-size: 2em;
+// $closebutton-lineheight: 1;
+// $closebutton-color: $dark-gray;
+// $closebutton-color-hover: $black;
+
+// 15. Drilldown
+// -------------
+
+// $drilldown-transition: transform 0.15s linear;
+// $drilldown-arrows: true;
+// $drilldown-arrow-color: $primary-color;
+// $drilldown-background: $white;
+
+// 16. Dropdown
+// ------------
+
+// $dropdown-padding: 1rem;
+// $dropdown-border: 1px solid $medium-gray;
+// $dropdown-font-size: 1rem;
+// $dropdown-width: 300px;
+// $dropdown-radius: $global-radius;
+// $dropdown-sizes: (
+//   tiny: 100px,
+//   small: 200px,
+//   large: 400px,
+// );
+
+// 17. Dropdown Menu
+// -----------------
+
+// $dropdownmenu-arrows: true;
+// $dropdownmenu-arrow-color: $anchor-color;
+// $dropdownmenu-min-width: 200px;
+// $dropdownmenu-background: $white;
+// $dropdownmenu-border: 1px solid $medium-gray;
+
+// 18. Flex Video
+// --------------
+
+// $flexvideo-margin-bottom: rem-calc(16);
+// $flexvideo-ratio: 4 by 3;
+// $flexvideo-ratio-widescreen: 16 by 9;
+
+// 19. Forms
+// ---------
+
+// $fieldset-border: 1px solid $medium-gray;
+// $fieldset-padding: rem-calc(20);
+// $fieldset-margin: rem-calc(18 0);
+// $legend-padding: rem-calc(0 3);
+// $form-spacing: rem-calc(16);
+// $helptext-color: $black;
+// $helptext-font-size: rem-calc(13);
+// $helptext-font-style: italic;
+// $input-prefix-color: $black;
+// $input-prefix-background: $light-gray;
+// $input-prefix-border: 1px solid $medium-gray;
+// $input-prefix-padding: 1rem;
+// $form-label-color: $black;
+// $form-label-font-size: rem-calc(14);
+// $form-label-font-weight: $global-weight-normal;
+// $form-label-line-height: 1.8;
+// $select-background: $white;
+// $select-triangle-color: $dark-gray;
+// $select-radius: $global-radius;
+// $input-color: $black;
+// $input-placeholder-color: $medium-gray;
+// $input-font-family: inherit;
+// $input-font-size: rem-calc(16);
+// $input-background: $white;
+// $input-background-focus: $white;
+// $input-background-disabled: $light-gray;
+// $input-border: 1px solid $medium-gray;
+// $input-border-focus: 1px solid $dark-gray;
+// $input-shadow: inset 0 1px 2px rgba($black, 0.1);
+// $input-shadow-focus: 0 0 5px $medium-gray;
+// $input-cursor-disabled: not-allowed;
+// $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
+// $input-number-spinners: true;
+// $input-radius: $global-radius;
+
+// 20. Label
+// ---------
+
+// $label-background: $primary-color;
+// $label-color: foreground($label-background);
+// $label-font-size: 0.8rem;
+// $label-padding: 0.33333rem 0.5rem;
+// $label-radius: $global-radius;
+
+// 21. Media Object
+// ----------------
+
+// $mediaobject-margin-bottom: $global-margin;
+// $mediaobject-section-padding: $global-padding;
+// $mediaobject-image-width-stacked: 100%;
+
+// 22. Menu
+// --------
+
+// $menu-margin: 0;
+// $menu-margin-nested: 1rem;
+// $menu-item-padding: 0.7rem 1rem;
+// $menu-item-color-active: $white;
+// $menu-item-background-active: map-get($foundation-palette, primary);
+// $menu-icon-spacing: 0.25rem;
+
+// 23. Meter
+// ---------
+
+// $meter-height: 1rem;
+// $meter-radius: $global-radius;
+// $meter-background: $medium-gray;
+// $meter-fill-good: $success-color;
+// $meter-fill-medium: $warning-color;
+// $meter-fill-bad: $alert-color;
+
+// 24. Off-canvas
+// --------------
+
+// $offcanvas-size: 250px;
+$offcanvas-background: $black;
+// $offcanvas-zindex: -1;
+// $offcanvas-transition-length: 0.5s;
+// $offcanvas-transition-timing: ease;
+// $offcanvas-fixed-reveal: true;
+// $offcanvas-exit-background: rgba($white, 0.25);
+// $maincontent-class: "off-canvas-content";
+$maincontent-shadow: none;
+
+// 25. Orbit
+// ---------
+
+// $orbit-bullet-background: $medium-gray;
+// $orbit-bullet-background-active: $dark-gray;
+// $orbit-bullet-diameter: 1.2rem;
+// $orbit-bullet-margin: 0.1rem;
+// $orbit-bullet-margin-top: 0.8rem;
+// $orbit-bullet-margin-bottom: 0.8rem;
+// $orbit-caption-background: rgba($black, 0.5);
+// $orbit-caption-padding: 1rem;
+// $orbit-control-background-hover: rgba($black, 0.5);
+// $orbit-control-padding: 1rem;
+// $orbit-control-zindex: 10;
+
+// 26. Pagination
+// --------------
+
+// $pagination-font-size: rem-calc(14);
+// $pagination-margin-bottom: $global-margin;
+// $pagination-item-color: $black;
+// $pagination-item-padding: rem-calc(3 10);
+// $pagination-item-spacing: rem-calc(1);
+// $pagination-radius: $global-radius;
+// $pagination-item-background-hover: $light-gray;
+// $pagination-item-background-current: $primary-color;
+// $pagination-item-color-current: foreground($pagination-item-background-current);
+// $pagination-item-color-disabled: $medium-gray;
+// $pagination-ellipsis-color: $black;
+// $pagination-mobile-items: false;
+// $pagination-arrows: true;
+
+// 27. Progress Bar
+// ----------------
+
+// $progress-height: 1rem;
+// $progress-background: $medium-gray;
+// $progress-margin-bottom: $global-margin;
+// $progress-meter-background: $primary-color;
+// $progress-radius: $global-radius;
+
+// 28. Reveal
+// ----------
+
+// $reveal-background: $white;
+// $reveal-width: 600px;
+// $reveal-max-width: $global-width;
+// $reveal-padding: $global-padding;
+// $reveal-border: 1px solid $medium-gray;
+// $reveal-radius: $global-radius;
+// $reveal-zindex: 1005;
+// $reveal-overlay-background: rgba($black, 0.45);
+
+// 29. Slider
+// ----------
+
+// $slider-width-vertical: 0.5rem;
+// $slider-transition: all 0.2s ease-in-out;
+// $slider-height: 0.5rem;
+// $slider-background: $light-gray;
+// $slider-fill-background: $medium-gray;
+// $slider-handle-height: 1.4rem;
+// $slider-handle-width: 1.4rem;
+// $slider-handle-background: $primary-color;
+// $slider-opacity-disabled: 0.25;
+// $slider-radius: $global-radius;
+
+// 30. Switch
+// ----------
+
+// $switch-background: $medium-gray;
+// $switch-background-active: $primary-color;
+// $switch-height: 2rem;
+// $switch-height-tiny: 1.5rem;
+// $switch-height-small: 1.75rem;
+// $switch-height-large: 2.5rem;
+// $switch-radius: $global-radius;
+// $switch-margin: $global-margin;
+// $switch-paddle-background: $white;
+// $switch-paddle-offset: 0.25rem;
+// $switch-paddle-radius: $global-radius;
+// $switch-paddle-transition: all 0.25s ease-out;
+
+// 31. Table
+// ---------
+
+// $table-background: $white;
+// $table-color-scale: 5%;
+// $table-border: 1px solid smart-scale($table-background, $table-color-scale);
+// $table-padding: rem-calc(8 10 10);
+// $table-hover-scale: 2%;
+// $table-row-hover: darken($table-background, $table-hover-scale);
+// $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
+// $table-striped-background: smart-scale($table-background, $table-color-scale);
+// $table-stripe: even;
+// $table-head-background: smart-scale($table-background, $table-color-scale / 2);
+// $table-foot-background: smart-scale($table-background, $table-color-scale);
+// $table-head-font-color: $body-font-color;
+// $show-header-for-stacked: false;
+
+// 32. Tabs
+// --------
+
+// $tab-margin: 0;
+// $tab-background: $white;
+// $tab-background-active: $light-gray;
+// $tab-item-font-size: rem-calc(12);
+// $tab-item-background-hover: $white;
+// $tab-item-padding: 1.25rem 1.5rem;
+// $tab-expand-max: 6;
+// $tab-content-background: $white;
+// $tab-content-border: $light-gray;
+// $tab-content-color: foreground($tab-background, $primary-color);
+// $tab-content-padding: 1rem;
+
+// 33. Thumbnail
+// -------------
+
+// $thumbnail-border: solid 4px $white;
+// $thumbnail-margin-bottom: $global-margin;
+// $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
+// $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
+// $thumbnail-transition: box-shadow 200ms ease-out;
+// $thumbnail-radius: $global-radius;
+
+// 34. Title Bar
+// -------------
+
+$titlebar-background: map-get($foundation-palette, primary);
+// $titlebar-color: $white;
+// $titlebar-padding: 0.5rem;
+// $titlebar-text-font-weight: bold;
+// $titlebar-icon-color: $white;
+// $titlebar-icon-color-hover: $medium-gray;
+// $titlebar-icon-spacing: 0.25rem;
+
+// 35. Tooltip
+// -----------
+
+// $has-tip-font-weight: $global-weight-bold;
+// $has-tip-border-bottom: dotted 1px $dark-gray;
+// $tooltip-background-color: $black;
+// $tooltip-color: $white;
+// $tooltip-padding: 0.75rem;
+// $tooltip-font-size: $small-font-size;
+// $tooltip-pip-width: 0.75rem;
+// $tooltip-pip-height: $tooltip-pip-width * 0.866;
+// $tooltip-radius: $global-radius;
+
+// 36. Top Bar
+// -----------
+
+// $topbar-padding: 0.5rem;
+// $topbar-background: $light-gray;
+// $topbar-submenu-background: $topbar-background;
+// $topbar-title-spacing: 1rem;
+// $topbar-input-width: 200px;
+// $topbar-unstack-breakpoint: medium;
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_sidebar.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_sidebar.scss
new file mode 100644
index 000000000..1e4034ded
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_sidebar.scss
@@ -0,0 +1,73 @@
+@mixin menu-title{
+  text-transform: uppercase;
+  letter-spacing: 5px;
+}
+
+.title-bar-title{
+  @include menu-title;
+}
+
+.sidebar{
+  color: $light-gray;
+
+  .title{
+    background-color: black;
+    background-color: map-get($foundation-palette, primary);
+
+    h1{
+      margin: 0;
+      padding: 0;
+      font-size: rem-calc(18);
+      font-weight: bold;
+    }
+
+    a{
+      display: block;
+      color: inherit;
+      padding: rem-calc(20) $global-padding;
+      @include menu-title;
+    }
+
+    @include show-for(large);
+  }
+
+  .main-menu{
+    border-top: 1px solid black;
+    border-bottom: 1px solid black;
+    a{
+      padding: $global-padding;
+      display: block;
+      color: $light-gray;
+      text-transform: uppercase;
+      font-weight: bold;
+      font-size: 0.9em;
+      border-left: 2px solid transparent;
+
+      transition: all 0.3s;
+
+      &.active {
+        border-color: map-get($foundation-palette, primary);
+        &, &:hover{
+          background-color: #101010;
+        }
+      }
+
+      &:hover {
+        background-color: #1a1a1a;
+      }
+    }
+  }
+
+  .session-box {
+    padding: $global-padding 0;
+
+    @include grid-row;
+
+    .admin-email {
+      @include grid-column(8);
+      text-overflow: ellipsis;
+      overflow: hidden;
+    }
+    .sign-out{ @include grid-column(4); }
+  }
+}
\ No newline at end of file
diff --git a/decidim-admin/app/assets/stylesheets/decidim/admin/_tables.scss b/decidim-admin/app/assets/stylesheets/decidim/admin/_tables.scss
new file mode 100644
index 000000000..47350f7ab
--- /dev/null
+++ b/decidim-admin/app/assets/stylesheets/decidim/admin/_tables.scss
@@ -0,0 +1,5 @@
+table{
+  td.actions, th.actions{
+    text-align: right;
+  }
+}
\ No newline at end of file
diff --git a/decidim-admin/app/constraints/admin/organization_dashboard_constraint.rb b/decidim-admin/app/constraints/admin/organization_dashboard_constraint.rb
new file mode 100644
index 000000000..929d09e0b
--- /dev/null
+++ b/decidim-admin/app/constraints/admin/organization_dashboard_constraint.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+module Admin
+  # A Rails routes constraint to only allow access to an Organization admin to
+  # the organization dashboard.
+  class OrganizationDashboardConstraint
+    # Initializes the contraint.
+    #
+    # request [Rack::Request]
+    def initialize(request)
+      @request = request
+    end
+
+    # Checks if the user can access the organization dashboard.
+    #
+    # Returns boolean.
+    def matches?
+      admin? && belongs_to_organization?
+    end
+
+    private
+
+    attr_reader :request
+
+    def admin?
+      user.admin?
+    end
+
+    def belongs_to_organization?
+      user.organization == request.env["decidim.current_organization"]
+    end
+
+    def user
+      return unless request.env["warden"].authenticate!(scope: "user")
+
+      @user ||= request.env["warden"].user("user")
+    end
+  end
+end
diff --git a/decidim-admin/app/controllers/decidim/admin/application_controller.rb b/decidim-admin/app/controllers/decidim/admin/application_controller.rb
new file mode 100644
index 000000000..8d73be5fa
--- /dev/null
+++ b/decidim-admin/app/controllers/decidim/admin/application_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+module Decidim
+  module Admin
+    # The main application controller that inherits from Rails.
+    class ApplicationController < ActionController::Base
+      protect_from_forgery with: :exception, prepend: true
+    end
+  end
+end
diff --git a/decidim-admin/app/controllers/decidim/admin/dashboard_controller.rb b/decidim-admin/app/controllers/decidim/admin/dashboard_controller.rb
new file mode 100644
index 000000000..e1a211d1a
--- /dev/null
+++ b/decidim-admin/app/controllers/decidim/admin/dashboard_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+require_dependency "decidim/admin/application_controller"
+
+module Decidim
+  module Admin
+    class DashboardController < ApplicationController
+    end
+  end
+end
diff --git a/decidim-admin/app/helpers/decidim/admin/application_helper.rb b/decidim-admin/app/helpers/decidim/admin/application_helper.rb
new file mode 100644
index 000000000..422196dcc
--- /dev/null
+++ b/decidim-admin/app/helpers/decidim/admin/application_helper.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+module Decidim
+  module Admin
+    # Custom helpers, scoped to the admin panel.
+    #
+    module ApplicationHelper
+      def title
+        "Decidim"
+      end
+    end
+  end
+end
diff --git a/decidim-admin/app/jobs/decidim/admin/application_job.rb b/decidim-admin/app/jobs/decidim/admin/application_job.rb
new file mode 100644
index 000000000..665c94bdb
--- /dev/null
+++ b/decidim-admin/app/jobs/decidim/admin/application_job.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+module Decidim
+  module Admin
+    # Custom ApplicationJob scoped to the admin panel.
+    #
+    class ApplicationJob < ActiveJob::Base
+    end
+  end
+end
diff --git a/decidim-admin/app/mailers/decidim/admin/application_mailer.rb b/decidim-admin/app/mailers/decidim/admin/application_mailer.rb
new file mode 100644
index 000000000..5e8f7f6c1
--- /dev/null
+++ b/decidim-admin/app/mailers/decidim/admin/application_mailer.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+module Decidim
+  module Admin
+    # Custom application mailer, scoped to the admin mailer.
+    #
+    class ApplicationMailer < ActionMailer::Base
+      default from: "from@example.com"
+      layout "mailer"
+    end
+  end
+end
diff --git a/decidim-admin/app/models/decidim/admin/application_record.rb b/decidim-admin/app/models/decidim/admin/application_record.rb
new file mode 100644
index 000000000..954bc4e6f
--- /dev/null
+++ b/decidim-admin/app/models/decidim/admin/application_record.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Decidim
+  module Admin
+    # Custom ApplicationRecord scoped to the Admin panel.
+    #
+    class ApplicationRecord < ActiveRecord::Base
+      self.abstract_class = true
+    end
+  end
+end
diff --git a/decidim-admin/app/views/decidim/admin/dashboard/show.html.erb b/decidim-admin/app/views/decidim/admin/dashboard/show.html.erb
new file mode 100644
index 000000000..e0e1deb6c
--- /dev/null
+++ b/decidim-admin/app/views/decidim/admin/dashboard/show.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title do %>
+  <h2><%= t("decidim.admin.titles.dashboard") %></h2>
+<% end %>
diff --git a/decidim-admin/app/views/decidim/admin/devise/mailers/password_change.html.erb b/decidim-admin/app/views/decidim/admin/devise/mailers/password_change.html.erb
new file mode 100644
index 000000000..b41daf476
--- /dev/null
+++ b/decidim-admin/app/views/decidim/admin/devise/mailers/password_change.html.erb
@@ -0,0 +1,3 @@
+<p>Hello <%= @resource.email %>!</p>
+
+<p>We're contacting you to notify you that your password has been changed.</p>
diff --git a/decidim-admin/app/views/decidim/admin/devise/mailers/reset_password_instructions.html.erb b/decidim-admin/app/views/decidim/admin/devise/mailers/reset_password_instructions.html.erb
new file mode 100644
index 000000000..f667dc12f
--- /dev/null
+++ b/decidim-admin/app/views/decidim/admin/devise/mailers/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
+<p>Hello <%= @resource.email %>!</p>
+
+<p>Someone has requested a link to change your password. You can do this through the link below.</p>
+
+<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
+
+<p>If you didn't request this, please ignore this email.</p>
+<p>Your password won't change until you access the link above and create a new one.</p>
diff --git a/decidim-admin/app/views/layouts/decidim/admin/_header.html.erb b/decidim-admin/app/views/layouts/decidim/admin/_header.html.erb
new file mode 100644
index 000000000..bfc90ed14
--- /dev/null
+++ b/decidim-admin/app/views/layouts/decidim/admin/_header.html.erb
@@ -0,0 +1,4 @@
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<%= csrf_meta_tags %>
+<%= stylesheet_link_tag    'decidim/admin', media: 'all', 'data-turbolinks-track': 'reload' %>
+<%= javascript_include_tag 'decidim/admin', 'data-turbolinks-track': 'reload' %>
diff --git a/decidim-admin/app/views/layouts/decidim/admin/_login_items.html.erb b/decidim-admin/app/views/layouts/decidim/admin/_login_items.html.erb
new file mode 100644
index 000000000..3982a4923
--- /dev/null
+++ b/decidim-admin/app/views/layouts/decidim/admin/_login_items.html.erb
@@ -0,0 +1,8 @@
+<div class="session-box">
+  <div class="admin-email">
+    <%= current_user.email %>
+  </div>
+  <div class="sign-out">
+    <%= link_to('Logout', decidim.destroy_user_session_path, method: :delete) %>
+  </div>
+</div>
diff --git a/decidim-admin/app/views/layouts/decidim/admin/_sidebar.html.erb b/decidim-admin/app/views/layouts/decidim/admin/_sidebar.html.erb
new file mode 100644
index 000000000..e5f861a2f
--- /dev/null
+++ b/decidim-admin/app/views/layouts/decidim/admin/_sidebar.html.erb
@@ -0,0 +1,13 @@
+<div class="title">
+  <h1>
+    <%= link_to root_path do %>
+      <%= title %>
+    <% end %>
+  </h1>
+</div>
+
+<nav class="main-menu">
+  <%= active_link_to t("menu.dashboard", scope: "decidim.admin"), root_path, active: :exact %>
+</nav>
+
+<%= render partial: 'layouts/decidim/admin/login_items' %>
diff --git a/decidim-admin/app/views/layouts/decidim/admin/application.html.erb b/decidim-admin/app/views/layouts/decidim/admin/application.html.erb
new file mode 100644
index 000000000..2d5a39034
--- /dev/null
+++ b/decidim-admin/app/views/layouts/decidim/admin/application.html.erb
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Decidim - admin</title>
+<%= render partial: 'layouts/decidim/admin/header' %>
+</head>
+
+<body>
+  <div class="off-canvas-wrapper">
+    <div class="off-canvas-wrapper-inner" data-off-canvas-wrapper>
+      <div class="off-canvas position-left reveal-for-large" id="my-info" data-off-canvas data-position="left">
+        <div class="sidebar">
+          <%= render partial: 'layouts/decidim/admin/sidebar' %>
+        </div>
+      </div>
+
+      <div class="off-canvas-content" data-off-canvas-content>
+        <div class="title-bar hide-for-large">
+          <div class="title-bar-left">
+            <button class="menu-icon" type="button" data-open="my-info"></button>
+            <span class="title-bar-title"><%= title %></span>
+          </div>
+        </div>
+        <div class="row main-content">
+          <div class="small-12 column">
+            <% if content_for?(:title) %>
+              <section class="page-title">
+                <%= content_for :title %>
+              </section>
+            <% end %>
+
+            <%= display_flash_messages %>
+            <%= yield %>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/decidim-admin/app/views/layouts/decidim/admin/login.html.erb b/decidim-admin/app/views/layouts/decidim/admin/login.html.erb
new file mode 100644
index 000000000..59acbe1a4
--- /dev/null
+++ b/decidim-admin/app/views/layouts/decidim/admin/login.html.erb
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Decidim - Login</title>
+    <%= render partial: 'layouts/decidim/admin/header' %>
+  </head>
+
+  <body class="login">
+    <div class="login-form-wrapper">
+      <div class="login-form">
+        <h1>Decidim</h1>
+        <%= display_flash_messages %>
+        <div class="login-form-inner">
+          <%= yield %>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/decidim-admin/bin/rails b/decidim-admin/bin/rails
new file mode 100755
index 000000000..78f659ee6
--- /dev/null
+++ b/decidim-admin/bin/rails
@@ -0,0 +1,14 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+# This command will automatically be run when you run "rails" with Rails gems
+# installed from the root of your application.
+
+ENGINE_ROOT = File.expand_path("../..", __FILE__)
+ENGINE_PATH = File.expand_path("../../lib/decidim/admin/engine", __FILE__)
+
+# Set up gems listed in the Gemfile.
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
+require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
+
+require "rails/all"
+require "rails/engine/commands"
diff --git a/decidim-admin/config/i18n-tasks.yml b/decidim-admin/config/i18n-tasks.yml
new file mode 100644
index 000000000..4cad6b582
--- /dev/null
+++ b/decidim-admin/config/i18n-tasks.yml
@@ -0,0 +1,120 @@
+# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
+
+# The "main" locale.
+base_locale: en
+## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
+# locales: [es, fr]
+## Reporting locale, default: en. Available: en, ru.
+# internal_locale: en
+
+# Read and write translations.
+data:
+  ## Translations are read from the file system. Supported format: YAML, JSON.
+  ## Provide a custom adapter:
+  # adapter: I18n::Tasks::Data::FileSystem
+
+  # Locale files or `File.find` patterns where translations are read from:
+  read:
+    ## Default:
+    # - config/locales/%{locale}.yml
+    ## More files:
+    # - config/locales/**/*.%{locale}.yml
+    ## Another gem (replace %#= with %=):
+    # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
+
+  # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
+  # `i18n-tasks normalize -p` will force move the keys according to these rules
+  write:
+    ## For example, write devise and simple form keys to their respective files:
+    # - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
+    ## Catch-all default:
+    # - config/locales/%{locale}.yml
+
+  ## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
+  # router: convervative_router
+
+  yaml:
+    write:
+      # do not wrap lines at 80 characters
+      line_width: -1
+
+  ## Pretty-print JSON:
+  # json:
+  #   write:
+  #     indent: '  '
+  #     space: ' '
+  #     object_nl: "\n"
+  #     array_nl: "\n"
+
+# Find translate calls
+search:
+  ## Paths or `File.find` patterns to search in:
+  # paths:
+  #  - app/
+
+  ## Root directories for relative keys resolution.
+  # relative_roots:
+  #   - app/controllers
+  #   - app/helpers
+  #   - app/mailers
+  #   - app/presenters
+  #   - app/views
+
+  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
+  ##   %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
+  exclude:
+    - app/assets/images
+    - app/assets/fonts
+
+  ## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
+  ## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
+  # only: ["*.rb", "*.html.slim"]
+
+  ## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
+  # strict: true
+
+  ## Multiple scanners can be used. Their results are merged.
+  ## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
+  ## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
+
+## Google Translate
+# translation:
+#   # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
+#   api_key: "AbC-dEf5"
+
+## Do not consider these keys missing:
+# ignore_missing:
+# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
+# - '{devise,simple_form}.*'
+
+## Consider these keys used:
+# ignore_unused:
+# - 'activerecord.attributes.*'
+# - '{devise,kaminari,will_paginate}.*'
+# - 'simple_form.{yes,no}'
+# - 'simple_form.{placeholders,hints,labels}.*'
+# - 'simple_form.{error_notification,required}.:'
+
+## Exclude these keys from the `i18n-tasks eq-base' report:
+# ignore_eq_base:
+#   all:
+#     - common.ok
+#   fr,es:
+#     - common.brand
+
+## Ignore these keys completely:
+# ignore:
+#  - kaminari.*
+
+## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
+## e.g. in case of a relative key defined in a helper method.
+## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
+#
+# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
+#        only: %w(*.html.haml *.html.slim),
+#        patterns: [['= title\b', '.page_title']] %>
+#
+# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
+#
+# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
+#        patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
diff --git a/decidim-admin/config/locales/en.yml b/decidim-admin/config/locales/en.yml
new file mode 100644
index 000000000..6951efb6b
--- /dev/null
+++ b/decidim-admin/config/locales/en.yml
@@ -0,0 +1,8 @@
+---
+en:
+  decidim:
+    admin:
+      menu:
+        dashboard: Dashboard
+      titles:
+        dashboard: Dashboard
diff --git a/decidim-admin/config/routes.rb b/decidim-admin/config/routes.rb
new file mode 100644
index 000000000..284c8d64b
--- /dev/null
+++ b/decidim-admin/config/routes.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+Decidim::Admin::Engine.routes.draw do
+  root to: "dashboard#show", constraints: ->(request) { Admin::OrganizationDashboardConstraint.new(request).matches? }
+end
diff --git a/decidim-admin/decidim-admin.gemspec b/decidim-admin/decidim-admin.gemspec
new file mode 100644
index 000000000..e0b30d8e9
--- /dev/null
+++ b/decidim-admin/decidim-admin.gemspec
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+$LOAD_PATH.push File.expand_path("../lib", __FILE__)
+
+# Maintain your gem's version:
+require_relative "../decidim-core/lib/decidim/core/version"
+
+# Describe your gem and declare its dependencies:
+Gem::Specification.new do |s|
+  s.name        = "decidim-admin"
+  s.version     = Decidim.version
+  s.authors     = ["Josep Jaume Rey Peroy", "Marc Riera Casals", "Oriol Gual Oliva"]
+  s.email       = ["josepjaume@gmail.com", "mrc2407@gmail.com", "oriolgual@gmail.com"]
+  s.homepage    = ""
+  s.summary     = "Organization administration"
+  s.description = "Organization administration to manage a single organization."
+  s.license     = "MIT"
+
+  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
+
+  s.add_dependency "decidim-core"
+  s.add_dependency "rails", Decidim.rails_version
+  s.add_dependency "devise", "~> 4.2"
+  s.add_dependency "rectify", "~> 0.6"
+  s.add_dependency "devise_invitable", "~> 1.7.0"
+  s.add_dependency "foundation-rails", "~> 6.2.3.0"
+  s.add_dependency "sass-rails", "~> 5.0.0"
+  s.add_dependency "jquery-rails", "~> 4.0"
+  s.add_dependency "turbolinks", Decidim.rails_version
+  s.add_dependency "jquery-turbolinks", "~> 2.1.0"
+  s.add_dependency "jbuilder", "~> 2.5"
+  s.add_dependency "foundation_rails_helper", "~> 2.0.0"
+  s.add_dependency "active_link_to", "~> 1.0.0"
+
+  s.add_development_dependency "factory_girl_rails"
+  s.add_development_dependency "database_cleaner", "~> 1.5.0"
+  s.add_development_dependency "capybara", "~> 2.4"
+  s.add_development_dependency "rspec-rails", "~> 3.5"
+  s.add_development_dependency "byebug"
+  s.add_development_dependency "wisper-rspec"
+  s.add_development_dependency "pg"
+  s.add_development_dependency "listen"
+  s.add_development_dependency "launchy"
+  s.add_development_dependency "i18n-tasks", "~> 0.9.5"
+end
diff --git a/decidim-admin/lib/decidim/admin.rb b/decidim-admin/lib/decidim/admin.rb
new file mode 100644
index 000000000..53e26a925
--- /dev/null
+++ b/decidim-admin/lib/decidim/admin.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+require "decidim/admin/engine"
+
+module Decidim
+  # This module contains all the logic related to a admin-wide
+  # administration panel. The scope of the domain is to be able
+  # to manage Organizations (tenants), as well as have a bird's
+  # eye view of the whole admin.
+  #
+  module Admin
+  end
+end
diff --git a/decidim-admin/lib/decidim/admin/engine.rb b/decidim-admin/lib/decidim/admin/engine.rb
new file mode 100644
index 000000000..229ca8892
--- /dev/null
+++ b/decidim-admin/lib/decidim/admin/engine.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+require "rails"
+require "active_support/all"
+
+require "decidim/core"
+require "devise"
+require "jquery-rails"
+require "sass-rails"
+require "turbolinks"
+require "jquery-turbolinks"
+require "foundation-rails"
+require "foundation_rails_helper"
+require "jbuilder"
+require "rectify"
+
+module Decidim
+  module Admin
+    # Decidim's core Rails Engine.
+    class Engine < ::Rails::Engine
+      isolate_namespace Decidim::Admin
+
+      config.to_prepare do
+        Rails.application.config.assets.precompile += %w(
+          decidim/admin.js
+          decidim/admin.css
+        )
+      end
+    end
+  end
+end
diff --git a/decidim-admin/lib/generators/decidim/admin/dummy_generator.rb b/decidim-admin/lib/generators/decidim/admin/dummy_generator.rb
new file mode 100644
index 000000000..bf17fb081
--- /dev/null
+++ b/decidim-admin/lib/generators/decidim/admin/dummy_generator.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+require "rails/generators"
+require_relative "../../../../lib/generators/decidim/app_generator"
+
+module Decidim
+  module Admin
+    module Generators
+      # Generates a dummy test Rails app for the given library folder. It uses
+      # the `AppGenerator` class to actually generate the Rails app, so this
+      # generator only sets the path and some flags.
+      #
+      # The Rails app will be installed with some flags to disable git, tests,
+      # Gemfile and other options. Refer to the `create_dummy_app` method to see
+      # all the flags passed to the `AppGenerator` class, which is the one that
+      # actually generates the Rails app.
+      #
+      # Remember that, for how generators work, actions are executed based on the
+      # definition order of the public methods.
+      class DummyGenerator < Rails::Generators::Base
+        desc "Generate dummy app for testing purposes"
+
+        class_option :lib_name, type: :string,
+                                desc: "The library where the dummy app will be installed"
+
+        class_option :migrate, type: :boolean, default: false,
+                               desc: "Run migrations after installing decidim"
+
+        def cleanup
+          remove_directory_if_exists(dummy_path)
+        end
+
+        def create_dummy_app
+          Decidim::Generators::AppGenerator.start [
+            dummy_path,
+            "--skip_gemfile",
+            "--skip-bundle",
+            "--skip-git",
+            "--skip-keeps",
+            "--skip-test",
+            "--migrate=#{options[:migrate]}"
+          ]
+        end
+
+        private
+
+        def dummy_path
+          ENV["DUMMY_PATH"] || "spec/#{short_lib_name}_dummy"
+        end
+
+        def remove_directory_if_exists(path)
+          remove_dir(path) if File.directory?(path)
+        end
+
+        def short_lib_name
+          options[:lib_name].split("/").last
+        end
+      end
+    end
+  end
+end
diff --git a/decidim-admin/lib/tasks/decidim/admin_tasks.rake b/decidim-admin/lib/tasks/decidim/admin_tasks.rake
new file mode 100644
index 000000000..93b571680
--- /dev/null
+++ b/decidim-admin/lib/tasks/decidim/admin_tasks.rake
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+# desc "Explaining what the task does"
+# task :decidim_admin do
+#   # Task goes here
+# end
diff --git a/decidim-admin/spec/factories.rb b/decidim-admin/spec/factories.rb
new file mode 100644
index 000000000..28750cfed
--- /dev/null
+++ b/decidim-admin/spec/factories.rb
@@ -0,0 +1 @@
+require_relative "../../decidim-core/spec/factories"
diff --git a/decidim-admin/spec/features/admin_access_control.rb b/decidim-admin/spec/features/admin_access_control.rb
new file mode 100644
index 000000000..753e69b90
--- /dev/null
+++ b/decidim-admin/spec/features/admin_access_control.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+describe "Admin dashboard access control", type: :feature do
+  let(:organization) { create(:organization) }
+
+  before do
+    login_as user, scope: :user
+    switch_to_host(organization.host)
+  end
+
+  context "a regular user" do
+    let(:user) { create(:user, organization: organization) }
+
+    it "can't access the admin dashboard" do
+      visit decidim_admin.root_path
+      expect(page.status_code).to eq(404)
+    end
+  end
+
+  context "an organization admin" do
+    let(:user) { create(:user, :admin, organization: organization) }
+
+    it "can access the admin dashboard" do
+      visit decidim_admin.root_path
+      expect(page.status_code).to eq(200)
+      expect(page).to have_content("Dashboard")
+    end
+  end
+
+  context "an admin from another organization" do
+    let(:other_organization) { create(:organization) }
+    let(:user) { create(:user, :admin, organization: other_organization) }
+
+    it "can't access the admin dashboard" do
+      visit decidim_admin.root_path
+      expect(page.status_code).to eq(404)
+    end
+  end
+end
diff --git a/decidim-admin/spec/features/admin_invite_spec.rb b/decidim-admin/spec/features/admin_invite_spec.rb
new file mode 100644
index 000000000..c16c0e366
--- /dev/null
+++ b/decidim-admin/spec/features/admin_invite_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+describe "Admin invite", type: :feature do
+  let(:form) do
+    Decidim::System::RegisterOrganizationForm.new(params)
+  end
+
+  let(:params) do
+    {
+      name: "Gotham City",
+      host: "decide.lvh.me",
+      organization_admin_email: "f.laguardia@gotham.gov"
+    }
+  end
+
+  before(:each) do
+    Decidim::System::RegisterOrganization.new(form).call
+    switch_to_host("decide.lvh.me")
+  end
+
+  describe "Accept an invitation" do
+    let(:email_link) do
+      last_delivery = ActionMailer::Base.deliveries.last.body.encoded
+      URI.extract(last_delivery).last
+    end
+
+    it "asks for a password and redirects to the organization dashboard" do
+      visit email_link
+
+      fill_in :user_password, with: "123456"
+      fill_in :user_password_confirmation, with: "123456"
+      find("*[type=submit]").click
+
+      expect(page).to have_content("Dashboard")
+    end
+  end
+end
diff --git a/decidim-admin/spec/i18n_spec.rb b/decidim-admin/spec/i18n_spec.rb
new file mode 100644
index 000000000..e7126127e
--- /dev/null
+++ b/decidim-admin/spec/i18n_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+require 'i18n/tasks'
+
+RSpec.describe 'I18n' do
+  let(:i18n) { I18n::Tasks::BaseTask.new }
+  let(:missing_keys) { i18n.missing_keys }
+  let(:unused_keys) { i18n.unused_keys }
+
+  it 'does not have missing keys' do
+    expect(missing_keys).to be_empty,
+      "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
+  end
+
+  it 'does not have unused keys' do
+    expect(unused_keys).to be_empty,
+      "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
+  end
+end
diff --git a/decidim-admin/spec/spec_helper.rb b/decidim-admin/spec/spec_helper.rb
new file mode 100644
index 000000000..531696bc8
--- /dev/null
+++ b/decidim-admin/spec/spec_helper.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+ENV["RAILS_ENV"] ||= "test"
+
+if ENV["CI"]
+  require "simplecov"
+  SimpleCov.start
+
+  require "codecov"
+  SimpleCov.formatter = SimpleCov::Formatter::Codecov
+end
+
+begin
+  require File.expand_path("../admin_dummy/config/environment", __FILE__)
+rescue LoadError
+  puts "Could not load dummy application. Please ensure you have run `bundle exec rake test_app`"
+  exit
+end
+
+require "rspec/rails"
+require "factory_girl_rails"
+require "database_cleaner"
+require "byebug"
+
+# Requires supporting files with custom matchers and macros, etc,
+# in ./support/ and its subdirectories.
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
+
+RSpec.configure do |config|
+  config.color = true
+  config.fail_fast = ENV["FAIL_FAST"] || false
+  config.infer_spec_type_from_file_location!
+  config.mock_with :rspec
+  config.raise_errors_for_deprecations!
+
+  # If you're not using ActiveRecord, or you'd prefer not to run each of your
+  # examples within a transaction, comment the following line or assign false
+  # instead of true.
+  config.use_transactional_fixtures = false
+end
diff --git a/decidim-admin/spec/support/action_mailer.rb b/decidim-admin/spec/support/action_mailer.rb
new file mode 100644
index 000000000..b9563a3bc
--- /dev/null
+++ b/decidim-admin/spec/support/action_mailer.rb
@@ -0,0 +1,5 @@
+RSpec.configure do |config|
+  config.before(:each) do
+    ActionMailer::Base.deliveries.clear
+  end
+end
diff --git a/decidim-admin/spec/support/capybara.rb b/decidim-admin/spec/support/capybara.rb
new file mode 100644
index 000000000..91b4e4bcc
--- /dev/null
+++ b/decidim-admin/spec/support/capybara.rb
@@ -0,0 +1,17 @@
+def switch_to_host(host = "lvh.me")
+  Capybara.app_host = "http://#{host}"
+end
+
+def switch_to_default_host
+  switch_to_host nil
+end
+
+Capybara.configure do |config|
+  config.always_include_port = true
+end
+
+RSpec.configure do |config|
+  config.before :each, type: :feature do
+    switch_to_default_host
+  end
+end
diff --git a/decidim-admin/spec/support/database_cleaner.rb b/decidim-admin/spec/support/database_cleaner.rb
new file mode 100644
index 000000000..2211979de
--- /dev/null
+++ b/decidim-admin/spec/support/database_cleaner.rb
@@ -0,0 +1,12 @@
+RSpec.configure do |config|
+  config.before(:suite) do
+    DatabaseCleaner.strategy = :transaction
+    DatabaseCleaner.clean_with(:truncation)
+  end
+
+  config.around(:each) do |example|
+    DatabaseCleaner.cleaning do
+      example.run
+    end
+  end
+end
diff --git a/decidim-admin/spec/support/factory_girl.rb b/decidim-admin/spec/support/factory_girl.rb
new file mode 100644
index 000000000..eec437fb3
--- /dev/null
+++ b/decidim-admin/spec/support/factory_girl.rb
@@ -0,0 +1,3 @@
+RSpec.configure do |config|
+  config.include FactoryGirl::Syntax::Methods
+end
diff --git a/decidim-admin/spec/support/warden.rb b/decidim-admin/spec/support/warden.rb
new file mode 100644
index 000000000..ea2a1a865
--- /dev/null
+++ b/decidim-admin/spec/support/warden.rb
@@ -0,0 +1,7 @@
+RSpec.configure do |config|
+  config.include Warden::Test::Helpers, type: :feature
+
+  config.after :each, type: :feature do
+    Warden.test_reset!
+  end
+end
diff --git a/decidim-admin/spec/support/wisper.rb b/decidim-admin/spec/support/wisper.rb
new file mode 100644
index 000000000..194b955ea
--- /dev/null
+++ b/decidim-admin/spec/support/wisper.rb
@@ -0,0 +1,5 @@
+require 'wisper/rspec/matchers'
+
+RSpec::configure do |config|
+  config.include(Wisper::RSpec::BroadcastMatcher)
+end
diff --git a/decidim-core/Gemfile b/decidim-core/Gemfile
index cb0eed623..f50953253 100644
--- a/decidim-core/Gemfile
+++ b/decidim-core/Gemfile
@@ -13,6 +13,7 @@ gemspec
 
 # To use a debugger
 # gem 'byebug', group: [:development, :test]
 
 gemspec path: "../decidim-system"
+gemspec path: "../decidim-admin"
 gemspec path: ".."
diff --git a/decidim-core/Gemfile.lock b/decidim-core/Gemfile.lock
index a3dae9684..485dca1eb 100644
--- a/decidim-core/Gemfile.lock
+++ b/decidim-core/Gemfile.lock
@@ -7,10 +7,28 @@ GIT
       activemodel (>= 4.1)
       activesupport (>= 4.1)
       railties (>= 4.1)
       tzinfo (~> 1.2, >= 1.2.2)
 
+PATH
+  remote: ../decidim-admin
+  specs:
+    decidim-admin (0.0.1.alpha2)
+      active_link_to (~> 1.0.0)
+      decidim-core
+      devise (~> 4.2)
+      devise_invitable (~> 1.7.0)
+      foundation-rails (~> 6.2.3.0)
+      foundation_rails_helper (~> 2.0.0)
+      jbuilder (~> 2.5)
+      jquery-rails (~> 4.0)
+      jquery-turbolinks (~> 2.1.0)
+      rails (~> 5.0.0)
+      rectify (~> 0.6)
+      sass-rails (~> 5.0.0)
+      turbolinks (~> 5.0.0)
+
 PATH
   remote: ../decidim-system
   specs:
     decidim-system (0.0.1.alpha2)
       active_link_to (~> 1.0.0)
@@ -29,10 +47,11 @@ PATH
 
 PATH
   remote: ..
   specs:
     decidim (0.0.1.alpha2)
+      decidim-admin (= 0.0.1.alpha2)
       decidim-core (= 0.0.1.alpha2)
       decidim-system (= 0.0.1.alpha2)
       rails (~> 5.0.0)
 
 PATH
@@ -312,10 +331,11 @@ DEPENDENCIES
   bundler (~> 1.12)
   byebug
   capybara (~> 2.4)
   database_cleaner (~> 1.5.0)
   decidim!
+  decidim-admin!
   decidim-core!
   decidim-system!
   factory_girl_rails
   foundation_rails_helper!
   i18n-tasks (~> 0.9.5)
diff --git a/decidim-core/app/controllers/decidim/devise/invitations_controller.rb b/decidim-core/app/controllers/decidim/devise/invitations_controller.rb
new file mode 100644
index 000000000..83ff4e952
--- /dev/null
+++ b/decidim-core/app/controllers/decidim/devise/invitations_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+module Decidim
+  module Devise
+    # This controller customizes the behaviour of Devise::Invitiable.
+    class InvitationsController < ::Devise::InvitationsController
+      # We don't users to create invitations, so we just redirect them to the
+      # homepage.
+      def authenticate_inviter!
+        redirect_to root_path
+      end
+
+      # Overwrite the method that returns the path after a user accepts an
+      # invitation. Using the param `invite_redirect` we can redirect the user
+      # to a custom path after it has accepted the invitation.
+      def after_accept_path_for(resource)
+        params[:invite_redirect] || after_sign_in_path_for(resource)
+      end
+    end
+  end
+end
diff --git a/decidim-core/app/controllers/decidim/devise/passwords_controller.rb b/decidim-core/app/controllers/decidim/devise/passwords_controller.rb
new file mode 100644
index 000000000..76cbca35b
--- /dev/null
+++ b/decidim-core/app/controllers/decidim/devise/passwords_controller.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Decidim
+  module Devise
+    # A controller so admins can recover their passwords.
+    class PasswordsController < Devise::PasswordsController
+    end
+  end
+end
diff --git a/decidim-core/app/controllers/decidim/devise/sessions_controller.rb b/decidim-core/app/controllers/decidim/devise/sessions_controller.rb
new file mode 100644
index 000000000..2c42fcd8f
--- /dev/null
+++ b/decidim-core/app/controllers/decidim/devise/sessions_controller.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Decidim
+  module Devise
+    # A controller so admins can log-in and use their backend.
+    class SessionsController < ::Devise::SessionsController
+    end
+  end
+end
diff --git a/decidim-core/app/mailers/decidim/decidim_devise_mailer.rb b/decidim-core/app/mailers/decidim/decidim_devise_mailer.rb
new file mode 100644
index 000000000..fc814592e
--- /dev/null
+++ b/decidim-core/app/mailers/decidim/decidim_devise_mailer.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+module Decidim
+  # A custom mailer for Devise so we can tweak the invitation instructions for
+  # each role.
+  class DecidimDeviseMailer < Devise::Mailer
+    def invitation_instructions(record, token, opts = {})
+      @token = token
+      devise_mail(record, record.invitation_instructions || :invitation_instructions, opts)
+    end
+  end
+end
diff --git a/decidim-core/app/middleware/decidim/current_organization.rb b/decidim-core/app/middleware/decidim/current_organization.rb
new file mode 100644
index 000000000..528ede261
--- /dev/null
+++ b/decidim-core/app/middleware/decidim/current_organization.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+module Decidim
+  # A middleware that enhances the request with the current organization based
+  # on the hostname.
+  class CurrentOrganization
+    # Initializes the Rack Middleware.
+    #
+    # app - The Rack application
+    def initialize(app)
+      @app = app
+    end
+
+    # Main entry point for a Rack Middleware.
+    #
+    # env - A Hash.
+    def call(env)
+      env["decidim.current_organization"] = detect_current_organization(env)
+      @app.call(env)
+    end
+
+    private
+
+    def detect_current_organization(env)
+      host = Rack::Request.new(env).host.downcase
+      Decidim::Organization.where(host: host).first
+    end
+  end
+end
diff --git a/decidim-core/app/models/decidim/user.rb b/decidim-core/app/models/decidim/user.rb
index b3885cbdc..b36be30f8 100644
--- a/decidim-core/app/models/decidim/user.rb
+++ b/decidim-core/app/models/decidim/user.rb
@@ -1,23 +1,37 @@
 # frozen_string_literal: true
+require_dependency "devise/models/decidim_validatable"
+
 module Decidim
   # A User is a citizen that wants to join the platform to participate.
   class User < ApplicationRecord
     devise :invitable, :database_authenticatable, :registerable,
-           :recoverable, :rememberable, :trackable, :validatable
+           :recoverable, :rememberable, :trackable, :decidim_validatable
 
     belongs_to :organization, foreign_key: "decidim_organization_id", class_name: Decidim::Organization
 
     ROLES = %w(admin moderator official).freeze
 
     validates :organization, presence: true
-    validates :roles, inclusion: { in: ROLES }, allow_blank: true
+    validate :all_roles_are_valid
+
+    # Public: Allows customizing the invitation instruction email content when
+    # inviting a user.
+    #
+    # Returns a String.
+    attr_accessor :invitation_instructions
 
     # Checks if the user is an admin in the organization it belongs to. This
     # should probably be deleted and add a system to authorize actions.
     #
     # Returns Booolean.
     def admin?
       roles.include?("admin")
     end
+
+    private
+
+    def all_roles_are_valid
+      errors.add(:roles, :invalid) unless roles.all? { |role| ROLES.include?(role) }
+    end
   end
 end
diff --git a/decidim-core/app/views/decidim/devise/invitations/edit.html.erb b/decidim-core/app/views/decidim/devise/invitations/edit.html.erb
new file mode 100644
index 000000000..a7c5ab05a
--- /dev/null
+++ b/decidim-core/app/views/decidim/devise/invitations/edit.html.erb
@@ -0,0 +1,16 @@
+<h2><%= t 'devise.invitations.edit.header' %></h2>
+
+<%= form_for resource, as: resource_name, url: invitation_path(resource_name, invite_redirect: params[:invite_redirect]), html: { method: :put } do |f| %>
+  <%= devise_error_messages! %>
+  <%= f.hidden_field :invitation_token %>
+
+  <% if f.object.class.require_password_on_accepting %>
+  <p><%= f.label :password %><br />
+  <%= f.password_field :password %></p>
+
+  <p><%= f.label :password_confirmation %><br />
+  <%= f.password_field :password_confirmation %></p>
+  <% end %>
+
+  <p><%= f.submit t("devise.invitations.edit.submit_button") %></p>
+<% end %>
diff --git a/decidim-core/app/views/decidim/devise/passwords/edit.html.erb b/decidim-core/app/views/decidim/devise/passwords/edit.html.erb
new file mode 100644
index 000000000..7e9c782b9
--- /dev/null
+++ b/decidim-core/app/views/decidim/devise/passwords/edit.html.erb
@@ -0,0 +1,23 @@
+<h2>Change your password</h2>
+
+<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
+  <%= devise_error_messages! %>
+  <%= f.hidden_field :reset_password_token %>
+
+  <div class="field">
+    <% if @minimum_password_length %>
+      <em>(<%= @minimum_password_length %> characters minimum)</em><br />
+    <% end %>
+    <%= f.password_field :password, autofocus: true, autocomplete: "off" %>
+  </div>
+
+  <div class="field">
+    <%= f.password_field :password_confirmation, autocomplete: "off" %>
+  </div>
+
+  <div class="actions">
+    <%= f.submit "Change my password" %>
+  </div>
+<% end %>
+
+<%= render "decidim/devise/shared/links" %>
diff --git a/decidim-core/app/views/decidim/devise/passwords/new.html.erb b/decidim-core/app/views/decidim/devise/passwords/new.html.erb
new file mode 100644
index 000000000..0ec6bd6a1
--- /dev/null
+++ b/decidim-core/app/views/decidim/devise/passwords/new.html.erb
@@ -0,0 +1,15 @@
+<h2>Forgot your password?</h2>
+
+<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
+  <%= devise_error_messages! %>
+
+  <div class="field">
+    <%= f.email_field :email, autofocus: true %>
+  </div>
+
+  <div class="actions">
+    <%= f.submit "Send me reset password instructions" %>
+  </div>
+<% end %>
+
+<%= render "decidim/devise/shared/links" %>
diff --git a/decidim-core/app/views/decidim/devise/sessions/new.html.erb b/decidim-core/app/views/decidim/devise/sessions/new.html.erb
new file mode 100644
index 000000000..78352a420
--- /dev/null
+++ b/decidim-core/app/views/decidim/devise/sessions/new.html.erb
@@ -0,0 +1,23 @@
+<h2>Log in</h2>
+
+<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
+  <div class="field">
+    <%= f.email_field :email, autofocus: true %>
+  </div>
+
+  <div class="field">
+    <%= f.password_field :password, autocomplete: "off" %>
+  </div>
+
+  <% if devise_mapping.rememberable? -%>
+    <div class="field">
+      <%= f.check_box :remember_me %>
+    </div>
+  <% end -%>
+
+  <div class="actions">
+    <%= f.submit "Log in" %>
+  </div>
+<% end %>
+
+<%= render "decidim/devise/shared/links" %>
diff --git a/decidim-core/app/views/decidim/devise/shared/_links.html.erb b/decidim-core/app/views/decidim/devise/shared/_links.html.erb
new file mode 100644
index 000000000..e6a3e4196
--- /dev/null
+++ b/decidim-core/app/views/decidim/devise/shared/_links.html.erb
@@ -0,0 +1,25 @@
+<%- if controller_name != 'sessions' %>
+  <%= link_to "Log in", new_session_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
+  <%= link_to "Sign up", new_registration_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
+  <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
+  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
+  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.omniauthable? %>
+  <%- resource_class.omniauth_providers.each do |provider| %>
+    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
+  <% end -%>
+<% end -%>
diff --git a/decidim-core/app/views/users/mailer/invitation_instructions.html.erb b/decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.html.erb
similarity index 80%
rename from decidim-core/app/views/users/mailer/invitation_instructions.html.erb
rename to decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.html.erb
index 4f31391af..f16cfc1e2 100644
--- a/decidim-core/app/views/users/mailer/invitation_instructions.html.erb
+++ b/decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.html.erb
@@ -1,10 +1,9 @@
 <p><%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %></p>
 
 <p><%= t("devise.mailer.invitation_instructions.someone_invited_you", application: Decidim.config.application_name) %></p>
-
-<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %></p>
+<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token, invite_redirect: decidim_admin.root_path, host: @resource.organization.host) %></p>
 
 <% if @resource.invitation_due_at %>
   <p><%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %></p>
 <% end %>
 
diff --git a/decidim-core/app/views/users/mailer/invitation_instructions.text.erb b/decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.text.erb
similarity index 77%
rename from decidim-core/app/views/users/mailer/invitation_instructions.text.erb
rename to decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.text.erb
index 2db4ae70b..92d33e8c0 100644
--- a/decidim-core/app/views/users/mailer/invitation_instructions.text.erb
+++ b/decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.text.erb
@@ -1,10 +1,10 @@
 <%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>
 
 <%= t("devise.mailer.invitation_instructions.someone_invited_you", application: Decidim.config.application_name) %>
 
-<%= accept_invitation_url(@resource, :invitation_token => @token) %>
+<%= accept_invitation_url(@resource, invitation_token: @token, invite_redirect: decidim_admin.root_path, host: @resource.organization.host) %>
 
 <% if @resource.invitation_due_at %>
   <%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>
 <% end %>
 
diff --git a/decidim-core/config/initializers/devise.rb b/decidim-core/config/initializers/devise.rb
index 03f3283c8..2889d364e 100644
--- a/decidim-core/config/initializers/devise.rb
+++ b/decidim-core/config/initializers/devise.rb
@@ -17,11 +17,11 @@ Devise.setup do |config|
   # note that it will be overwritten if you use your own mailer class
   # with default "from" parameter.
   config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
 
   # Configure the class responsible to send e-mails.
-  # config.mailer = 'Devise::Mailer'
+  config.mailer = "Decidim::DecidimDeviseMailer"
 
   # Configure the parent class responsible to send e-mails.
   config.parent_mailer = "Decidim::ApplicationMailer"
 
   config.parent_controller = "Decidim::ApplicationController"
@@ -163,11 +163,11 @@ Devise.setup do |config|
   # config.invited_by_counter_cache = :invitations_count
 
   # Auto-login after the user accepts the invite. If this is false,
   # the user will need to manually log in after accepting the invite.
   # Default: true
-  # config.allow_insecure_sign_in_after_accept = false
+  config.allow_insecure_sign_in_after_accept = true
 
   # ==> Configuration for :confirmable
   # A period that the user is allowed to access the website even without
   # confirming their account. For instance, if set to 2.days, the user will be
   # able to access the website for two days without confirming their account,
diff --git a/decidim-core/lib/decidim/core/engine.rb b/decidim-core/lib/decidim/core/engine.rb
index 323fa72bc..b95cf195f 100644
--- a/decidim-core/lib/decidim/core/engine.rb
+++ b/decidim-core/lib/decidim/core/engine.rb
@@ -24,8 +24,12 @@ module Decidim
       initializer "decidim.action_controller" do |_app|
         ActiveSupport.on_load :action_controller do
           helper Decidim::LayoutHelper
         end
       end
+
+      initializer "decidim.middleware" do |app|
+        app.config.middleware.use Decidim::CurrentOrganization
+      end
     end
   end
 end
diff --git a/decidim-core/lib/devise/models/decidim_validatable.rb b/decidim-core/lib/devise/models/decidim_validatable.rb
new file mode 100644
index 000000000..7d08063f3
--- /dev/null
+++ b/decidim-core/lib/devise/models/decidim_validatable.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+module Devise
+  module Models
+    # Validatable creates all needed validations for a user email and password.
+    # Automatically validate if the email is present, unique and its format is
+    # valid. Also tests presence of password, confirmation and length.
+    #
+    # == Options
+    #
+    # Validatable adds the following options to devise_for:
+    #
+    #   * +email_regexp+: the regular expression used to validate e-mails;
+    #   * +password_length+: a range expressing password length. Defaults to 8..72.
+    #
+    module DecidimValidatable
+      # All validations used by this module.
+      VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
+                     :validates_confirmation_of, :validates_length_of].freeze
+
+      def self.required_fields(_klass)
+        []
+      end
+
+      def self.included(base)
+        base.extend ClassMethods
+        assert_validations_api!(base)
+
+        base.class_eval do
+          validates_presence_of   :email, if: :email_required?
+          validates_uniqueness_of :email, allow_blank: true, if: :email_changed?, scope: :organization
+          validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?
+
+          validates_presence_of     :password, if: :password_required?
+          validates_confirmation_of :password, if: :password_required?
+          validates_length_of       :password, within: password_length, allow_blank: true
+        end
+      end
+
+      def self.assert_validations_api!(base) #:nodoc:
+        unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
+
+        unless unavailable_validations.empty?
+          raise "Could not use :validatable module since #{base} does not respond " \
+                "to the following methods: #{unavailable_validations.to_sentence}."
+        end
+      end
+
+      protected
+
+      # Checks whether a password is needed or not. For validations only.
+      # Passwords are always required if it's a new record, or if the password
+      # or confirmation are being set somewhere.
+      def password_required?
+        !persisted? || !password.nil? || !password_confirmation.nil?
+      end
+
+      def email_required?
+        true
+      end
+
+      # Methods to be injected at class level.
+      module ClassMethods
+        Devise::Models.config(self, :email_regexp, :password_length)
+      end
+    end
+  end
+end
diff --git a/decidim-core/lib/generators/decidim/install_generator.rb b/decidim-core/lib/generators/decidim/install_generator.rb
index 254841ae7..73f40e5d6 100644
--- a/decidim-core/lib/generators/decidim/install_generator.rb
+++ b/decidim-core/lib/generators/decidim/install_generator.rb
@@ -16,10 +16,11 @@ module Decidim
       class_option :migrate, type: :boolean, default: false,
                              desc: "Run migrations after installing decidim"
 
       def install
         route "mount Decidim::System::Engine => '/system'"
+        route "mount Decidim::Admin::Engine => '/admin'"
         route "mount Decidim::Core::Engine => '/'"
       end
 
       def copy_migrations
         rake "railties:install:migrations"
@@ -73,12 +74,14 @@ module Decidim
       end
 
       private
 
       def prepare_database
-        rake "db:environment:set RAILS_ENV=development"
-        rake "db:drop:all"
+        unless ENV["CI"]
+          rake "db:environment:set RAILS_ENV=development"
+          rake "db:drop:all"
+        end
         rake "db:create"
         rake "db:migrate"
         rake "db:test:prepare"
       end
     end
diff --git a/decidim-core/spec/middleware/decidim/current_organization_spec.rb b/decidim-core/spec/middleware/decidim/current_organization_spec.rb
new file mode 100644
index 000000000..6bb6c3835
--- /dev/null
+++ b/decidim-core/spec/middleware/decidim/current_organization_spec.rb
@@ -0,0 +1,30 @@
+require "spec_helper"
+
+module Decidim
+  describe CurrentOrganization do
+    let(:app) { ->(env) { [200, env, "app"] } }
+    let(:env) { Rack::MockRequest.env_for("https://#{host}", {}) }
+    let(:host) { "city.domain.org" }
+    let(:middleware) { described_class.new(app) }
+
+    context "when an organization exists for the current host" do
+      let!(:organization) { create(:organization, host: host) }
+
+      it "sets the organization" do
+        _code, new_env = middleware.call(env)
+
+        expect(new_env["decidim.current_organization"]).to eq(organization)
+      end
+    end
+
+    context "when no orgazanization exists for the current host" do
+      let!(:organization) { create(:organization, host: 'fake.host.com') }
+
+      it "doesn't set the organization" do
+        _code, new_env = middleware.call(env)
+
+        expect(new_env["decidim.current_organization"]).to be_nil
+      end
+    end
+  end
+end
diff --git a/decidim-core/spec/models/decidim/user_spec.rb b/decidim-core/spec/models/decidim/user_spec.rb
index 10edb0d51..f16c1376b 100644
--- a/decidim-core/spec/models/decidim/user_spec.rb
+++ b/decidim-core/spec/models/decidim/user_spec.rb
@@ -6,10 +6,41 @@ describe "User", :db do
 
   it "is valid" do
     expect(user).to be_valid
   end
 
+  context "with roles" do
+    let(:user) { build(:user, :admin) }
+
+    it "is still valid" do
+      expect(user).to be_valid
+    end
+
+    context "with an invalid role" do
+      let(:user) { build(:user, roles: ["foo"]) }
+
+      it "is not valid" do
+        expect(user).to_not be_valid
+      end
+    end
+  end
+
+  describe "validation scopes" do
+    context "when a user with the same email exists in another organization" do
+      let(:email) { "foo@bar.com" }
+      let(:user) { build(:user, email: email) }
+
+      before do
+        create(:user, email: email)
+      end
+
+      it "is valid" do
+        expect(user).to be_valid
+      end
+    end
+  end
+
   describe "admin?" do
     context "when the user is an admin" do
       before do
         user.roles = ["admin"]
       end
diff --git a/decidim-system/Gemfile.lock b/decidim-system/Gemfile.lock
index bc40318a2..1fdc2ef0e 100644
--- a/decidim-system/Gemfile.lock
+++ b/decidim-system/Gemfile.lock
@@ -27,13 +27,28 @@ PATH
 
 PATH
   remote: ..
   specs:
     decidim (0.0.1.alpha2)
+      decidim-admin (= 0.0.1.alpha2)
       decidim-core (= 0.0.1.alpha2)
       decidim-system (= 0.0.1.alpha2)
       rails (~> 5.0.0)
+    decidim-admin (0.0.1.alpha2)
+      active_link_to (~> 1.0.0)
+      decidim-core
+      devise (~> 4.2)
+      devise_invitable (~> 1.7.0)
+      foundation-rails (~> 6.2.3.0)
+      foundation_rails_helper (~> 2.0.0)
+      jbuilder (~> 2.5)
+      jquery-rails (~> 4.0)
+      jquery-turbolinks (~> 2.1.0)
+      rails (~> 5.0.0)
+      rectify (~> 0.6)
+      sass-rails (~> 5.0.0)
+      turbolinks (~> 5.0.0)
 
 PATH
   remote: .
   specs:
     decidim-system (0.0.1.alpha2)
diff --git a/decidim-system/app/commands/decidim/system/register_organization.rb b/decidim-system/app/commands/decidim/system/register_organization.rb
index 1825c4022..33acbdec7 100644
--- a/decidim-system/app/commands/decidim/system/register_organization.rb
+++ b/decidim-system/app/commands/decidim/system/register_organization.rb
@@ -42,10 +42,12 @@ module Decidim
       def invite_admin(organization)
         Decidim::User.invite!(
           email: form.organization_admin_email,
           organization: organization,
           roles: ["admin"]
-        )
+        ) do |invitable|
+          invitable.invitation_instructions = :organization_admin_invitation_instructions
+        end
       end
     end
   end
 end
diff --git a/decidim-system/app/helpers/decidim/system/application_helper.rb b/decidim-system/app/helpers/decidim/system/application_helper.rb
index aac9187ee..6552517b9 100644
--- a/decidim-system/app/helpers/decidim/system/application_helper.rb
+++ b/decidim-system/app/helpers/decidim/system/application_helper.rb
@@ -5,12 +5,8 @@ module Decidim
     #
     module ApplicationHelper
       def title
         "Decidim"
       end
-
-      def field_name(model, field)
-        st "models.#{model}.fields.#{field}"
-      end
     end
   end
 end
diff --git a/decidim-system/spec/commands/decidim/system/register_organization_spec.rb b/decidim-system/spec/commands/decidim/system/register_organization_spec.rb
index 671b9f981..b48b6f8fb 100644
--- a/decidim-system/spec/commands/decidim/system/register_organization_spec.rb
+++ b/decidim-system/spec/commands/decidim/system/register_organization_spec.rb
@@ -39,10 +39,17 @@ module Decidim
             expect(admin.email).to eq("f.laguardia@gotham.gov")
             expect(admin.organization.name).to eq("Gotham City")
             expect(admin).to be_admin
             expect(admin).to be_created_by_invite
           end
+
+          it "sends a custom email" do
+            expect { command.call }.to change { ActionMailer::Base.deliveries.count }.by(1)
+
+            last_delivery_body = ActionMailer::Base.deliveries.last.body.encoded
+            expect(last_delivery_body).to include(URI.encode_www_form(["/admin"]))
+          end
         end
 
         context "when the form is invalid" do
           let(:params) do
             {
diff --git a/decidim-system/spec/support/action_mailer.rb b/decidim-system/spec/support/action_mailer.rb
new file mode 100644
index 000000000..b9563a3bc
--- /dev/null
+++ b/decidim-system/spec/support/action_mailer.rb
@@ -0,0 +1,5 @@
+RSpec.configure do |config|
+  config.before(:each) do
+    ActionMailer::Base.deliveries.clear
+  end
+end
diff --git a/decidim.gemspec b/decidim.gemspec
index 87ba404c3..044e82de5 100644
--- a/decidim.gemspec
+++ b/decidim.gemspec
@@ -22,10 +22,11 @@ Gem::Specification.new do |spec|
   spec.executables   = ["decidim"]
   spec.require_paths = ["lib"]
 
   spec.add_dependency "decidim-core", Decidim.version
   spec.add_dependency "decidim-system", Decidim.version
+  spec.add_dependency "decidim-admin", Decidim.version
   spec.add_dependency "rails", Decidim.rails_version
 
   spec.add_development_dependency "bundler", "~> 1.12"
   spec.add_development_dependency "rake", "~> 11.0"
   spec.add_development_dependency "rspec", "~> 3.0"
diff --git a/lib/decidim.rb b/lib/decidim.rb
index f8bec565e..0ac616ba8 100644
--- a/lib/decidim.rb
+++ b/lib/decidim.rb
@@ -1,7 +1,8 @@
 # frozen_string_literal: true
 require "decidim/core"
 require "decidim/system"
+require "decidim/admin"
 
 # Module declaration.
 module Decidim
 end

Vulnerable code:     decidim-system (0.0.1.alpha2)
DECIDIM_GEMS = %w(core system).freeze
           :recoverable, :rememberable, :trackable, :validatable
    validates :roles, inclusion: { in: ROLES }, allow_blank: true

<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %></p>
<%= accept_invitation_url(@resource, :invitation_token => @token) %>
  # config.mailer = 'Devise::Mailer'
  # config.allow_insecure_sign_in_after_accept = false
        rake "db:environment:set RAILS_ENV=development"
        rake "db:drop:all"
        )

      def field_name(model, field)
        st "models.#{model}.fields.#{field}"
      end
Fixed code: gemspec path: "decidim-system"
gemspec path: "decidim-admin"
      decidim-admin (= 0.0.1.alpha2)

PATH
  remote: decidim-admin
  specs:
    decidim-admin (0.0.1.alpha2)
PATH
  remote: decidim-system
  specs:
    decidim-system (0.0.1.alpha2)
      active_link_to (~> 1.0.0)
      decidim-core
      devise (~> 4.2)
      devise_invitable (~> 1.7.0)
      foundation-rails (~> 6.2.3.0)
      foundation_rails_helper (~> 2.0.0)
      jbuilder (~> 2.5)
      jquery-rails (~> 4.0)
      jquery-turbolinks (~> 2.1.0)
      rails (~> 5.0.0)
      rectify (~> 0.6)
      sass-rails (~> 5.0.0)
      turbolinks (~> 5.0.0)

    launchy (2.4.3)
      addressable (~> 2.3)
  decidim-admin!
  decidim-system!
  launchy
DECIDIM_GEMS = %w(core system admin).freeze
eval(File.read(File.dirname(__FILE__) + "/../common_gemfile.rb"))

# Declare your gem's dependencies in decidim-admin.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec

# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.

# To use a debugger
# gem 'byebug', group: [:development, :test]

gemspec path: "../decidim-core"
gemspec path: ".."
GIT
  remote: https://github.com/sgruhier/foundation_rails_helper
  revision: d8819b3a5f9d82251aac7559e0d07505c9777717
  specs:
    foundation_rails_helper (2.0.0)
      actionpack (>= 4.1)
      activemodel (>= 4.1)
      activesupport (>= 4.1)
      railties (>= 4.1)
      tzinfo (~> 1.2, >= 1.2.2)

PATH
  remote: ../decidim-core
  specs:
    decidim-core (0.0.1.alpha2)
      active_link_to (~> 1.0.0)
      devise (~> 4.2)
      foundation-rails (~> 6.2.3.0)
      foundation_rails_helper (~> 2.0.0)
      jbuilder (~> 2.5)
      jquery-rails (~> 4.0)
      jquery-turbolinks (~> 2.1.0)
      rails (~> 5.0.0)
      rectify (~> 0.6)
      sass-rails (~> 5.0.0)
      turbolinks (~> 5.0.0)

PATH
  remote: ..
  specs:
    decidim (0.0.1.alpha2)
      decidim-admin (= 0.0.1.alpha2)
      decidim-core (= 0.0.1.alpha2)
      decidim-system (= 0.0.1.alpha2)
      rails (~> 5.0.0)
    decidim-system (0.0.1.alpha2)
      active_link_to (~> 1.0.0)
      decidim-core
      devise (~> 4.2)
      devise_invitable (~> 1.7.0)
      foundation-rails (~> 6.2.3.0)
      foundation_rails_helper (~> 2.0.0)
      jbuilder (~> 2.5)
      jquery-rails (~> 4.0)
      jquery-turbolinks (~> 2.1.0)
      rails (~> 5.0.0)
      rectify (~> 0.6)
      sass-rails (~> 5.0.0)
      turbolinks (~> 5.0.0)

PATH
  remote: .
  specs:
    decidim-admin (0.0.1.alpha2)
      active_link_to (~> 1.0.0)
      decidim-core
      devise (~> 4.2)
      devise_invitable (~> 1.7.0)
      foundation-rails (~> 6.2.3.0)
      foundation_rails_helper (~> 2.0.0)
      jbuilder (~> 2.5)
      jquery-rails (~> 4.0)
      jquery-turbolinks (~> 2.1.0)
      rails (~> 5.0.0)
      rectify (~> 0.6)
      sass-rails (~> 5.0.0)
      turbolinks (~> 5.0.0)

GEM
  remote: https://rubygems.org/
  specs:
    actioncable (5.0.0.1)
      actionpack (= 5.0.0.1)
      nio4r (~> 1.2)
      websocket-driver (~> 0.6.1)
    actionmailer (5.0.0.1)
      actionpack (= 5.0.0.1)
      actionview (= 5.0.0.1)
      activejob (= 5.0.0.1)
      mail (~> 2.5, >= 2.5.4)
      rails-dom-testing (~> 2.0)
    actionpack (5.0.0.1)
      actionview (= 5.0.0.1)
      activesupport (= 5.0.0.1)
      rack (~> 2.0)
      rack-test (~> 0.6.3)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.0, >= 1.0.2)
    actionview (5.0.0.1)
      activesupport (= 5.0.0.1)
      builder (~> 3.1)
      erubis (~> 2.7.0)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.0, >= 1.0.2)
    active_link_to (1.0.3)
      actionpack
    activejob (5.0.0.1)
      activesupport (= 5.0.0.1)
      globalid (>= 0.3.6)
    activemodel (5.0.0.1)
      activesupport (= 5.0.0.1)
    activerecord (5.0.0.1)
      activemodel (= 5.0.0.1)
      activesupport (= 5.0.0.1)
      arel (~> 7.0)
    activesupport (5.0.0.1)
      concurrent-ruby (~> 1.0, >= 1.0.2)
      i18n (~> 0.7)
      minitest (~> 5.1)
      tzinfo (~> 1.1)
    addressable (2.4.0)
    arel (7.1.2)
    ast (2.3.0)
    axiom-types (0.1.1)
      descendants_tracker (~> 0.0.4)
      ice_nine (~> 0.11.0)
      thread_safe (~> 0.3, >= 0.3.1)
    babel-source (5.8.35)
    babel-transpiler (0.7.0)
      babel-source (>= 4.0, < 6)
      execjs (~> 2.0)
    bcrypt (3.1.11)
    builder (3.2.2)
    byebug (9.0.5)
    capybara (2.9.1)
      addressable
      mime-types (>= 1.16)
      nokogiri (>= 1.3.3)
      rack (>= 1.0.0)
      rack-test (>= 0.5.4)
      xpath (~> 2.0)
    coercible (1.0.0)
      descendants_tracker (~> 0.0.1)
    concurrent-ruby (1.0.2)
    database_cleaner (1.5.3)
    descendants_tracker (0.0.4)
      thread_safe (~> 0.3, >= 0.3.1)
    devise (4.2.0)
      bcrypt (~> 3.0)
      orm_adapter (~> 0.1)
      railties (>= 4.1.0, < 5.1)
      responders
      warden (~> 1.2.3)
    devise_invitable (1.7.0)
      actionmailer (>= 4.0.0)
      devise (>= 4.0.0)
    diff-lcs (1.2.5)
    easy_translate (0.5.0)
      json
      thread
      thread_safe
    equalizer (0.0.11)
    erubis (2.7.0)
    execjs (2.7.0)
    factory_girl (4.7.0)
      activesupport (>= 3.0.0)
    factory_girl_rails (4.7.0)
      factory_girl (~> 4.7.0)
      railties (>= 3.0.0)
    ffi (1.9.14)
    foundation-rails (6.2.3.0)
      railties (>= 3.1.0)
      sass (>= 3.3.0, < 3.5)
      sprockets-es6 (>= 0.9.0)
    globalid (0.3.7)
      activesupport (>= 4.1.0)
    highline (1.7.8)
    i18n (0.7.0)
    i18n-tasks (0.9.5)
      activesupport (>= 4.0.2)
      ast (>= 2.1.0)
      easy_translate (>= 0.5.0)
      erubis
      highline (>= 1.7.3)
      i18n
      parser (>= 2.2.3.0)
      term-ansicolor (>= 1.3.2)
      terminal-table (>= 1.5.1)
    ice_nine (0.11.2)
    jbuilder (2.6.0)
      activesupport (>= 3.0.0, < 5.1)
      multi_json (~> 1.2)
    jquery-rails (4.2.1)
      rails-dom-testing (>= 1, < 3)
      railties (>= 4.2.0)
      thor (>= 0.14, < 2.0)
    jquery-turbolinks (2.1.0)
      railties (>= 3.1.0)
      turbolinks
    json (2.0.2)
    launchy (2.4.3)
      addressable (~> 2.3)
    listen (3.1.5)
      rb-fsevent (~> 0.9, >= 0.9.4)
      rb-inotify (~> 0.9, >= 0.9.7)
      ruby_dep (~> 1.2)
    loofah (2.0.3)
      nokogiri (>= 1.5.9)
    mail (2.6.4)
      mime-types (>= 1.16, < 4)
    method_source (0.8.2)
    mime-types (3.1)
      mime-types-data (~> 3.2015)
    mime-types-data (3.2016.0521)
    mini_portile2 (2.1.0)
    minitest (5.9.1)
    multi_json (1.12.1)
    nio4r (1.2.1)
    nokogiri (1.6.8)
      mini_portile2 (~> 2.1.0)
      pkg-config (~> 1.1.7)
    orm_adapter (0.5.0)
    parser (2.3.1.4)
      ast (~> 2.2)
    pg (0.19.0)
    pkg-config (1.1.7)
    rack (2.0.1)
    rack-test (0.6.3)
      rack (>= 1.0)
    rails (5.0.0.1)
      actioncable (= 5.0.0.1)
      actionmailer (= 5.0.0.1)
      actionpack (= 5.0.0.1)
      actionview (= 5.0.0.1)
      activejob (= 5.0.0.1)
      activemodel (= 5.0.0.1)
      activerecord (= 5.0.0.1)
      activesupport (= 5.0.0.1)
      bundler (>= 1.3.0, < 2.0)
      railties (= 5.0.0.1)
      sprockets-rails (>= 2.0.0)
    rails-dom-testing (2.0.1)
      activesupport (>= 4.2.0, < 6.0)
      nokogiri (~> 1.6.0)
    rails-html-sanitizer (1.0.3)
      loofah (~> 2.0)
    railties (5.0.0.1)
      actionpack (= 5.0.0.1)
      activesupport (= 5.0.0.1)
      method_source
      rake (>= 0.8.7)
      thor (>= 0.18.1, < 2.0)
    rake (11.3.0)
    rb-fsevent (0.9.7)
    rb-inotify (0.9.7)
      ffi (>= 0.5.0)
    rectify (0.6.1)
      activemodel (>= 4.1.0)
      activerecord (>= 4.1.0)
      activesupport (>= 4.1.0)
      virtus (~> 1.0.5)
      wisper (>= 1.6.1)
    responders (2.3.0)
      railties (>= 4.2.0, < 5.1)
    rspec (3.5.0)
      rspec-core (~> 3.5.0)
      rspec-expectations (~> 3.5.0)
      rspec-mocks (~> 3.5.0)
    rspec-core (3.5.3)
      rspec-support (~> 3.5.0)
    rspec-expectations (3.5.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.5.0)
    rspec-mocks (3.5.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.5.0)
    rspec-rails (3.5.2)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      railties (>= 3.0)
      rspec-core (~> 3.5.0)
      rspec-expectations (~> 3.5.0)
      rspec-mocks (~> 3.5.0)
      rspec-support (~> 3.5.0)
    rspec-support (3.5.0)
    ruby_dep (1.4.0)
    sass (3.4.22)
    sass-rails (5.0.6)
      railties (>= 4.0.0, < 6)
      sass (~> 3.1)
      sprockets (>= 2.8, < 4.0)
      sprockets-rails (>= 2.0, < 4.0)
      tilt (>= 1.1, < 3)
    sprockets (3.7.0)
      concurrent-ruby (~> 1.0)
      rack (> 1, < 3)
    sprockets-es6 (0.9.2)
      babel-source (>= 5.8.11)
      babel-transpiler
      sprockets (>= 3.0.0)
    sprockets-rails (3.2.0)
      actionpack (>= 4.0)
      activesupport (>= 4.0)
      sprockets (>= 3.0.0)
    term-ansicolor (1.3.2)
      tins (~> 1.0)
    terminal-table (1.7.3)
      unicode-display_width (~> 1.1.1)
    thor (0.19.1)
    thread (0.2.2)
    thread_safe (0.3.5)
    tilt (2.0.5)
    tins (1.12.0)
    turbolinks (5.0.1)
      turbolinks-source (~> 5)
    turbolinks-source (5.0.0)
    tzinfo (1.2.2)
      thread_safe (~> 0.1)
    unicode-display_width (1.1.1)
    virtus (1.0.5)
      axiom-types (~> 0.1)
      coercible (~> 1.0)
      descendants_tracker (~> 0.0, >= 0.0.3)
      equalizer (~> 0.0, >= 0.0.9)
    warden (1.2.6)
      rack (>= 1.0)
    websocket-driver (0.6.4)
      websocket-extensions (>= 0.1.0)
    websocket-extensions (0.1.2)
    wisper (1.6.1)
    wisper-rspec (0.0.2)
    xpath (2.0.0)
      nokogiri (~> 1.3)

PLATFORMS
  ruby

DEPENDENCIES
  bundler (~> 1.12)
  byebug
  capybara (~> 2.4)
  database_cleaner (~> 1.5.0)
  decidim!
  decidim-admin!
  decidim-core!
  factory_girl_rails
  foundation_rails_helper!
  i18n-tasks (~> 0.9.5)
  launchy
  listen
  pg
  rake (~> 11.0)
  rspec (~> 3.0)
  rspec-rails (~> 3.5)
  wisper-rspec

RUBY VERSION
   ruby 2.3.1p112

BUNDLED WITH
   1.13.1
# Decidim::Admin
Short description and motivation.

## Usage
How to use my plugin.

## Installation
Add this line to your application's Gemfile:

```ruby
gem 'decidim-admin'
```

And then execute:
```bash
$ bundle
```

Or install it yourself as:
```bash
$ gem install decidim-admin
```

## Contributing
Contribution directions go here.

## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
# frozen_string_literal: true
begin
  require "bundler/setup"
rescue LoadError
  puts "You must `gem install bundler` and `bundle install` to run rake tasks"
end

require_relative "../decidim-core/lib/decidim/testing_support/common_rake"
require "rdoc/task"

RDoc::Task.new(:rdoc) do |rdoc|
  rdoc.rdoc_dir = "rdoc"
  rdoc.title    = "Decidim::Admin"
  rdoc.options << "--line-numbers"
  rdoc.rdoc_files.include("README.md")
  rdoc.rdoc_files.include("lib/**/*.rb")
end

load "rails/tasks/statistics.rake"

require "bundler/gem_tasks"

task default: :test

desc "Generates a dummy app for testing"
task :test_app do
  ENV["LIB_NAME"] = "decidim/admin"
  Rake::Task["common:test_app"].invoke
end

task test: :test_app do
  Dir.chdir(File.dirname(__FILE__).to_s) do
    sh "rspec spec"
  end
end
//= link_directory ../javascripts/decidim/admin.js
//= link_directory ../stylesheets/decidim/admin.css
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= require foundation
//= require_self

$(function(){ $(document).foundation(); });

//= require turbolinks
@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';

@import "admin/foundation_and_overrides";
@import "admin/layout";
@import "admin/login";
@import "admin/sidebar";
@import "admin/tables";
@import "admin/actions";
.actions{
  text-align: right;

  &.title{
    margin-bottom: $global-margin;
    font-size: 1.2em;
  }
}
@charset "utf-8";

@import "settings";
@import "foundation";

// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
//
// @import 'motion-ui/motion-ui';

// We include everything by default.  To slim your CSS, remove components you don't use.

@include foundation-global-styles;
@include foundation-flex-grid;
@include foundation-typography;
@include foundation-button;
@include foundation-forms;
@include foundation-visibility-classes;
@include foundation-flex-classes;
@include foundation-accordion;
@include foundation-accordion-menu;
@include foundation-badge;
@include foundation-breadcrumbs;
@include foundation-button-group;
@include foundation-callout;
@include foundation-close-button;
@include foundation-drilldown-menu;
@include foundation-dropdown;
@include foundation-dropdown-menu;
@include foundation-flex-video;
@include foundation-label;
@include foundation-media-object;
@include foundation-menu;
@include foundation-menu-icon;
@include foundation-off-canvas;
@include foundation-orbit;
@include foundation-pagination;
@include foundation-progress-bar;
@include foundation-slider;
@include foundation-sticky;
@include foundation-reveal;
@include foundation-switch;
@include foundation-table;
@include foundation-tabs;
@include foundation-thumbnail;
@include foundation-title-bar;
@include foundation-tooltip;
@include foundation-top-bar;

// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
//
// @include motion-ui-transitions;
// @include motion-ui-animations;
.off-canvas, .sidebar, .off-canvas-wrapper{
  min-height: 100vh;
}

.off-canvas{
  color: #fff;
}

.main-content{
  padding: $global-padding 0;
  min-height: 100vh;
}

.page-title{
  margin-bottom: $global-margin;
}
body.login{
  background: $light-gray;
  position: relative;

  .login-form-wrapper{
    @include grid-row;
    @include flex;

    height: 100vh;

    .login-form{
      @include grid-column(12);
      @include grid-column-position(center);

      @include breakpoint(medium){
        @include grid-column(8);
        @include grid-column-position(center);
      }

      @include breakpoint(large){
        @include grid-column(6);
        @include grid-column-position(center);
      }
    }

    @include breakpoint(medium){
      @include flex-align(center, middle);
      transform: translateY(-8%);
    }

    .login-form-inner{
      background-color: $white;
      padding: $global-padding;
    }
  }
}
//  Foundation for Sites Settings
//  -----------------------------
//
//  Table of Contents:
//
//   1. Global
//   2. Breakpoints
//   3. The Grid
//   4. Base Typography
//   5. Typography Helpers
//   6. Abide
//   7. Accordion
//   8. Accordion Menu
//   9. Badge
//  10. Breadcrumbs
//  11. Button
//  12. Button Group
//  13. Callout
//  14. Close Button
//  15. Drilldown
//  16. Dropdown
//  17. Dropdown Menu
//  18. Flex Video
//  19. Forms
//  20. Label
//  21. Media Object
//  22. Menu
//  23. Meter
//  24. Off-canvas
//  25. Orbit
//  26. Pagination
//  27. Progress Bar
//  28. Reveal
//  29. Slider
//  30. Switch
//  31. Table
//  32. Tabs
//  33. Thumbnail
//  34. Title Bar
//  35. Tooltip
//  36. Top Bar

@import "util/util";

// 1. Global
// ---------

// $global-font-size: 100%;
// $global-width: rem-calc(1200);
// $global-lineheight: 1.5;
$foundation-palette: (
  primary: #2199e8,
  secondary: #777,
  success: #3adb76,
  warning: #ffae00,
  alert: #ec5840,
);
$light-gray: #e6e6e6;
// $medium-gray: #cacaca;
// $dark-gray: #8a8a8a;
$black: #232323;
// $white: #fefefe;
// $body-background: $white;
// $body-font-color: $black;
$body-font-family: 'Montserrat', sans-serif;
// $body-antialiased: true;
// $global-margin: 1rem;
// $global-padding: 1rem;
// $global-weight-normal: normal;
// $global-weight-bold: bold;
// $global-radius: 0;
// $global-text-direction: ltr;
$global-flexbox: true;
// $print-transparent-backgrounds: true;

@include add-foundation-colors;

// 2. Breakpoints
// --------------

// $breakpoints: (
//   small: 0,
//   medium: 640px,
//   large: 1024px,
//   xlarge: 1200px,
//   xxlarge: 1440px,
// );
// $breakpoint-classes: (small medium large);

// 3. The Grid
// -----------

// $grid-row-width: $global-width;
// $grid-column-count: 12;
// $grid-column-gutter: (
//   small: 20px,
//   medium: 30px,
// );
// $grid-column-align-edge: true;
// $block-grid-max: 8;

// 4. Base Typography
// ------------------

// $header-font-family: $body-font-family;
// $header-font-weight: $global-weight-normal;
// $header-font-style: normal;
// $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
// $header-sizes: (
//   small: (
//     'h1': 24,
//     'h2': 20,
//     'h3': 19,
//     'h4': 18,
//     'h5': 17,
//     'h6': 16,
//   ),
//   medium: (
//     'h1': 48,
//     'h2': 40,
//     'h3': 31,
//     'h4': 25,
//     'h5': 20,
//     'h6': 16,
//   ),
// );
// $header-color: inherit;
// $header-lineheight: 1.4;
// $header-margin-bottom: 0.5rem;
// $header-text-rendering: optimizeLegibility;
// $small-font-size: 80%;
// $header-small-font-color: $medium-gray;
// $paragraph-lineheight: 1.6;
// $paragraph-margin-bottom: 1rem;
// $paragraph-text-rendering: optimizeLegibility;
// $code-color: $black;
// $code-font-family: $font-family-monospace;
// $code-font-weight: $global-weight-normal;
// $code-background: $light-gray;
// $code-border: 1px solid $medium-gray;
// $code-padding: rem-calc(2 5 1);
// $anchor-color: $primary-color;
// $anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
// $anchor-text-decoration: none;
// $anchor-text-decoration-hover: none;
// $hr-width: $global-width;
// $hr-border: 1px solid $medium-gray;
// $hr-margin: rem-calc(20) auto;
// $list-lineheight: $paragraph-lineheight;
// $list-margin-bottom: $paragraph-margin-bottom;
// $list-style-type: disc;
// $list-style-position: outside;
// $list-side-margin: 1.25rem;
// $list-nested-side-margin: 1.25rem;
// $defnlist-margin-bottom: 1rem;
// $defnlist-term-weight: $global-weight-bold;
// $defnlist-term-margin-bottom: 0.3rem;
// $blockquote-color: $dark-gray;
// $blockquote-padding: rem-calc(9 20 0 19);
// $blockquote-border: 1px solid $medium-gray;
// $cite-font-size: rem-calc(13);
// $cite-color: $dark-gray;
// $keystroke-font: $font-family-monospace;
// $keystroke-color: $black;
// $keystroke-background: $light-gray;
// $keystroke-padding: rem-calc(2 4 0);
// $keystroke-radius: $global-radius;
// $abbr-underline: 1px dotted $black;

// 5. Typography Helpers
// ---------------------

// $lead-font-size: $global-font-size * 1.25;
// $lead-lineheight: 1.6;
// $subheader-lineheight: 1.4;
// $subheader-color: $dark-gray;
// $subheader-font-weight: $global-weight-normal;
// $subheader-margin-t#23262Cop: 0.2rem;
// $subheader-margin-bottom: 0.5rem;
// $stat-font-size: 2.5rem;

// 6. Abide
// --------

// $abide-inputs: true;
// $abide-labels: true;
// $input-background-invalid: map-get($foundation-palette, alert);
// $form-label-color-invalid: map-get($foundation-palette, alert);
// $input-error-color: map-get($foundation-palette, alert);
// $input-error-font-size: rem-calc(12);
// $input-error-font-weight: $global-weight-bold;

// 7. Accordion
// ------------

// $accordion-background: $white;
// $accordion-plusminus: true;
// $accordion-item-color: foreground($accordion-background, $primary-color);
// $accordion-item-background-hover: $light-gray;
// $accordion-item-padding: 1.25rem 1rem;
// $accordion-content-background: $white;
// $accordion-content-border: 1px solid $light-gray;
// $accordion-content-color: foreground($accordion-content-background, $body-font-color);
// $accordion-content-padding: 1rem;

// 8. Accordion Menu
// -----------------

// $accordionmenu-arrows: true;
// $accordionmenu-arrow-color: $primary-color;

// 9. Badge
// --------

// $badge-background: $primary-color;
// $badge-color: foreground($badge-background);
// $badge-padding: 0.3em;
// $badge-minwidth: 2.1em;
// $badge-font-size: 0.6rem;

// 10. Breadcrumbs
// ---------------

// $breadcrumbs-margin: 0 0 $global-margin 0;
// $breadcrumbs-item-font-size: rem-calc(11);
// $breadcrumbs-item-color: $primary-color;
// $breadcrumbs-item-color-current: $black;
// $breadcrumbs-item-color-disabled: $medium-gray;
// $breadcrumbs-item-margin: 0.75rem;
// $breadcrumbs-item-uppercase: true;
// $breadcrumbs-item-slash: true;

// 11. Button
// ----------

// $button-padding: 0.85em 1em;
$button-margin: 0;
// $button-fill: solid;
// $button-background: $primary-color;
// $button-background-hover: scale-color($button-background, $lightness: -15%);
// $button-color: $white;
// $button-color-alt: $black;
// $button-radius: $global-radius;
// $button-sizes: (
//   tiny: 0.6rem,
//   small: 0.75rem,
//   default: 0.9rem,
//   large: 1.25rem,
// );
// $button-opacity-disabled: 0.25;

// 12. Button Group
// ----------------

// $buttongroup-margin: 1rem;
// $buttongroup-spacing: 1px;
// $buttongroup-child-selector: ".button";
// $buttongroup-expand-max: 6;

// 13. Callout
// -----------

// $callout-background: $white;
// $callout-background-fade: 85%;
// $callout-border: 1px solid rgba($black, 0.25);
// $callout-margin: 0 0 1rem 0;
// $callout-padding: 1rem;
// $callout-font-color: $body-font-color;
// $callout-font-color-alt: $body-background;
// $callout-radius: $global-radius;
// $callout-link-tint: 30%;

// 14. Close Button
// ----------------

// $closebutton-position: right top;
// $closebutton-offset-horizontal: 1rem;
// $closebutton-offset-vertical: 0.5rem;
// $closebutton-size: 2em;
// $closebutton-lineheight: 1;
// $closebutton-color: $dark-gray;
// $closebutton-color-hover: $black;

// 15. Drilldown
// -------------

// $drilldown-transition: transform 0.15s linear;
// $drilldown-arrows: true;
// $drilldown-arrow-color: $primary-color;
// $drilldown-background: $white;

// 16. Dropdown
// ------------

// $dropdown-padding: 1rem;
// $dropdown-border: 1px solid $medium-gray;
// $dropdown-font-size: 1rem;
// $dropdown-width: 300px;
// $dropdown-radius: $global-radius;
// $dropdown-sizes: (
//   tiny: 100px,
//   small: 200px,
//   large: 400px,
// );

// 17. Dropdown Menu
// -----------------

// $dropdownmenu-arrows: true;
// $dropdownmenu-arrow-color: $anchor-color;
// $dropdownmenu-min-width: 200px;
// $dropdownmenu-background: $white;
// $dropdownmenu-border: 1px solid $medium-gray;

// 18. Flex Video
// --------------

// $flexvideo-margin-bottom: rem-calc(16);
// $flexvideo-ratio: 4 by 3;
// $flexvideo-ratio-widescreen: 16 by 9;

// 19. Forms
// ---------

// $fieldset-border: 1px solid $medium-gray;
// $fieldset-padding: rem-calc(20);
// $fieldset-margin: rem-calc(18 0);
// $legend-padding: rem-calc(0 3);
// $form-spacing: rem-calc(16);
// $helptext-color: $black;
// $helptext-font-size: rem-calc(13);
// $helptext-font-style: italic;
// $input-prefix-color: $black;
// $input-prefix-background: $light-gray;
// $input-prefix-border: 1px solid $medium-gray;
// $input-prefix-padding: 1rem;
// $form-label-color: $black;
// $form-label-font-size: rem-calc(14);
// $form-label-font-weight: $global-weight-normal;
// $form-label-line-height: 1.8;
// $select-background: $white;
// $select-triangle-color: $dark-gray;
// $select-radius: $global-radius;
// $input-color: $black;
// $input-placeholder-color: $medium-gray;
// $input-font-family: inherit;
// $input-font-size: rem-calc(16);
// $input-background: $white;
// $input-background-focus: $white;
// $input-background-disabled: $light-gray;
// $input-border: 1px solid $medium-gray;
// $input-border-focus: 1px solid $dark-gray;
// $input-shadow: inset 0 1px 2px rgba($black, 0.1);
// $input-shadow-focus: 0 0 5px $medium-gray;
// $input-cursor-disabled: not-allowed;
// $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
// $input-number-spinners: true;
// $input-radius: $global-radius;

// 20. Label
// ---------

// $label-background: $primary-color;
// $label-color: foreground($label-background);
// $label-font-size: 0.8rem;
// $label-padding: 0.33333rem 0.5rem;
// $label-radius: $global-radius;

// 21. Media Object
// ----------------

// $mediaobject-margin-bottom: $global-margin;
// $mediaobject-section-padding: $global-padding;
// $mediaobject-image-width-stacked: 100%;

// 22. Menu
// --------

// $menu-margin: 0;
// $menu-margin-nested: 1rem;
// $menu-item-padding: 0.7rem 1rem;
// $menu-item-color-active: $white;
// $menu-item-background-active: map-get($foundation-palette, primary);
// $menu-icon-spacing: 0.25rem;

// 23. Meter
// ---------

// $meter-height: 1rem;
// $meter-radius: $global-radius;
// $meter-background: $medium-gray;
// $meter-fill-good: $success-color;
// $meter-fill-medium: $warning-color;
// $meter-fill-bad: $alert-color;

// 24. Off-canvas
// --------------

// $offcanvas-size: 250px;
$offcanvas-background: $black;
// $offcanvas-zindex: -1;
// $offcanvas-transition-length: 0.5s;
// $offcanvas-transition-timing: ease;
// $offcanvas-fixed-reveal: true;
// $offcanvas-exit-background: rgba($white, 0.25);
// $maincontent-class: "off-canvas-content";
$maincontent-shadow: none;

// 25. Orbit
// ---------

// $orbit-bullet-background: $medium-gray;
// $orbit-bullet-background-active: $dark-gray;
// $orbit-bullet-diameter: 1.2rem;
// $orbit-bullet-margin: 0.1rem;
// $orbit-bullet-margin-top: 0.8rem;
// $orbit-bullet-margin-bottom: 0.8rem;
// $orbit-caption-background: rgba($black, 0.5);
// $orbit-caption-padding: 1rem;
// $orbit-control-background-hover: rgba($black, 0.5);
// $orbit-control-padding: 1rem;
// $orbit-control-zindex: 10;

// 26. Pagination
// --------------

// $pagination-font-size: rem-calc(14);
// $pagination-margin-bottom: $global-margin;
// $pagination-item-color: $black;
// $pagination-item-padding: rem-calc(3 10);
// $pagination-item-spacing: rem-calc(1);
// $pagination-radius: $global-radius;
// $pagination-item-background-hover: $light-gray;
// $pagination-item-background-current: $primary-color;
// $pagination-item-color-current: foreground($pagination-item-background-current);
// $pagination-item-color-disabled: $medium-gray;
// $pagination-ellipsis-color: $black;
// $pagination-mobile-items: false;
// $pagination-arrows: true;

// 27. Progress Bar
// ----------------

// $progress-height: 1rem;
// $progress-background: $medium-gray;
// $progress-margin-bottom: $global-margin;
// $progress-meter-background: $primary-color;
// $progress-radius: $global-radius;

// 28. Reveal
// ----------

// $reveal-background: $white;
// $reveal-width: 600px;
// $reveal-max-width: $global-width;
// $reveal-padding: $global-padding;
// $reveal-border: 1px solid $medium-gray;
// $reveal-radius: $global-radius;
// $reveal-zindex: 1005;
// $reveal-overlay-background: rgba($black, 0.45);

// 29. Slider
// ----------

// $slider-width-vertical: 0.5rem;
// $slider-transition: all 0.2s ease-in-out;
// $slider-height: 0.5rem;
// $slider-background: $light-gray;
// $slider-fill-background: $medium-gray;
// $slider-handle-height: 1.4rem;
// $slider-handle-width: 1.4rem;
// $slider-handle-background: $primary-color;
// $slider-opacity-disabled: 0.25;
// $slider-radius: $global-radius;

// 30. Switch
// ----------

// $switch-background: $medium-gray;
// $switch-background-active: $primary-color;
// $switch-height: 2rem;
// $switch-height-tiny: 1.5rem;
// $switch-height-small: 1.75rem;
// $switch-height-large: 2.5rem;
// $switch-radius: $global-radius;
// $switch-margin: $global-margin;
// $switch-paddle-background: $white;
// $switch-paddle-offset: 0.25rem;
// $switch-paddle-radius: $global-radius;
// $switch-paddle-transition: all 0.25s ease-out;

// 31. Table
// ---------

// $table-background: $white;
// $table-color-scale: 5%;
// $table-border: 1px solid smart-scale($table-background, $table-color-scale);
// $table-padding: rem-calc(8 10 10);
// $table-hover-scale: 2%;
// $table-row-hover: darken($table-background, $table-hover-scale);
// $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
// $table-striped-background: smart-scale($table-background, $table-color-scale);
// $table-stripe: even;
// $table-head-background: smart-scale($table-background, $table-color-scale / 2);
// $table-foot-background: smart-scale($table-background, $table-color-scale);
// $table-head-font-color: $body-font-color;
// $show-header-for-stacked: false;

// 32. Tabs
// --------

// $tab-margin: 0;
// $tab-background: $white;
// $tab-background-active: $light-gray;
// $tab-item-font-size: rem-calc(12);
// $tab-item-background-hover: $white;
// $tab-item-padding: 1.25rem 1.5rem;
// $tab-expand-max: 6;
// $tab-content-background: $white;
// $tab-content-border: $light-gray;
// $tab-content-color: foreground($tab-background, $primary-color);
// $tab-content-padding: 1rem;

// 33. Thumbnail
// -------------

// $thumbnail-border: solid 4px $white;
// $thumbnail-margin-bottom: $global-margin;
// $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
// $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
// $thumbnail-transition: box-shadow 200ms ease-out;
// $thumbnail-radius: $global-radius;

// 34. Title Bar
// -------------

$titlebar-background: map-get($foundation-palette, primary);
// $titlebar-color: $white;
// $titlebar-padding: 0.5rem;
// $titlebar-text-font-weight: bold;
// $titlebar-icon-color: $white;
// $titlebar-icon-color-hover: $medium-gray;
// $titlebar-icon-spacing: 0.25rem;

// 35. Tooltip
// -----------

// $has-tip-font-weight: $global-weight-bold;
// $has-tip-border-bottom: dotted 1px $dark-gray;
// $tooltip-background-color: $black;
// $tooltip-color: $white;
// $tooltip-padding: 0.75rem;
// $tooltip-font-size: $small-font-size;
// $tooltip-pip-width: 0.75rem;
// $tooltip-pip-height: $tooltip-pip-width * 0.866;
// $tooltip-radius: $global-radius;

// 36. Top Bar
// -----------

// $topbar-padding: 0.5rem;
// $topbar-background: $light-gray;
// $topbar-submenu-background: $topbar-background;
// $topbar-title-spacing: 1rem;
// $topbar-input-width: 200px;
// $topbar-unstack-breakpoint: medium;
@mixin menu-title{
  text-transform: uppercase;
  letter-spacing: 5px;
}

.title-bar-title{
  @include menu-title;
}

.sidebar{
  color: $light-gray;

  .title{
    background-color: black;
    background-color: map-get($foundation-palette, primary);

    h1{
      margin: 0;
      padding: 0;
      font-size: rem-calc(18);
      font-weight: bold;
    }

    a{
      display: block;
      color: inherit;
      padding: rem-calc(20) $global-padding;
      @include menu-title;
    }

    @include show-for(large);
  }

  .main-menu{
    border-top: 1px solid black;
    border-bottom: 1px solid black;
    a{
      padding: $global-padding;
      display: block;
      color: $light-gray;
      text-transform: uppercase;
      font-weight: bold;
      font-size: 0.9em;
      border-left: 2px solid transparent;

      transition: all 0.3s;

      &.active {
        border-color: map-get($foundation-palette, primary);
        &, &:hover{
          background-color: #101010;
        }
      }

      &:hover {
        background-color: #1a1a1a;
      }
    }
  }

  .session-box {
    padding: $global-padding 0;

    @include grid-row;

    .admin-email {
      @include grid-column(8);
      text-overflow: ellipsis;
      overflow: hidden;
    }
    .sign-out{ @include grid-column(4); }
  }
}
table{
  td.actions, th.actions{
    text-align: right;
  }
}
# frozen_string_literal: true
module Admin
  # A Rails routes constraint to only allow access to an Organization admin to
  # the organization dashboard.
  class OrganizationDashboardConstraint
    # Initializes the contraint.
    #
    # request [Rack::Request]
    def initialize(request)
      @request = request
    end

    # Checks if the user can access the organization dashboard.
    #
    # Returns boolean.
    def matches?
      admin? && belongs_to_organization?
    end

    private

    attr_reader :request

    def admin?
      user.admin?
    end

    def belongs_to_organization?
      user.organization == request.env["decidim.current_organization"]
    end

    def user
      return unless request.env["warden"].authenticate!(scope: "user")

      @user ||= request.env["warden"].user("user")
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Admin
    # The main application controller that inherits from Rails.
    class ApplicationController < ActionController::Base
      protect_from_forgery with: :exception, prepend: true
    end
  end
end
# frozen_string_literal: true
require_dependency "decidim/admin/application_controller"

module Decidim
  module Admin
    class DashboardController < ApplicationController
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Admin
    # Custom helpers, scoped to the admin panel.
    #
    module ApplicationHelper
      def title
        "Decidim"
      end
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Admin
    # Custom ApplicationJob scoped to the admin panel.
    #
    class ApplicationJob < ActiveJob::Base
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Admin
    # Custom application mailer, scoped to the admin mailer.
    #
    class ApplicationMailer < ActionMailer::Base
      default from: "from@example.com"
      layout "mailer"
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Admin
    # Custom ApplicationRecord scoped to the Admin panel.
    #
    class ApplicationRecord < ActiveRecord::Base
      self.abstract_class = true
    end
  end
end
<% content_for :title do %>
  <h2><%= t("decidim.admin.titles.dashboard") %></h2>
<% end %>
<p>Hello <%= @resource.email %>!</p>

<p>We're contacting you to notify you that your password has been changed.</p>
<p>Hello <%= @resource.email %>!</p>

<p>Someone has requested a link to change your password. You can do this through the link below.</p>

<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>

<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= csrf_meta_tags %>
<%= stylesheet_link_tag    'decidim/admin', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'decidim/admin', 'data-turbolinks-track': 'reload' %>
<div class="session-box">
  <div class="admin-email">
    <%= current_user.email %>
  </div>
  <div class="sign-out">
    <%= link_to('Logout', decidim.destroy_user_session_path, method: :delete) %>
  </div>
</div>
<div class="title">
  <h1>
    <%= link_to root_path do %>
      <%= title %>
    <% end %>
  </h1>
</div>

<nav class="main-menu">
  <%= active_link_to t("menu.dashboard", scope: "decidim.admin"), root_path, active: :exact %>
</nav>

<%= render partial: 'layouts/decidim/admin/login_items' %>
<!DOCTYPE html>
<html>
<head>
<title>Decidim - admin</title>
<%= render partial: 'layouts/decidim/admin/header' %>
</head>

<body>
  <div class="off-canvas-wrapper">
    <div class="off-canvas-wrapper-inner" data-off-canvas-wrapper>
      <div class="off-canvas position-left reveal-for-large" id="my-info" data-off-canvas data-position="left">
        <div class="sidebar">
          <%= render partial: 'layouts/decidim/admin/sidebar' %>
        </div>
      </div>

      <div class="off-canvas-content" data-off-canvas-content>
        <div class="title-bar hide-for-large">
          <div class="title-bar-left">
            <button class="menu-icon" type="button" data-open="my-info"></button>
            <span class="title-bar-title"><%= title %></span>
          </div>
        </div>
        <div class="row main-content">
          <div class="small-12 column">
            <% if content_for?(:title) %>
              <section class="page-title">
                <%= content_for :title %>
              </section>
            <% end %>

            <%= display_flash_messages %>
            <%= yield %>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <title>Decidim - Login</title>
    <%= render partial: 'layouts/decidim/admin/header' %>
  </head>

  <body class="login">
    <div class="login-form-wrapper">
      <div class="login-form">
        <h1>Decidim</h1>
        <%= display_flash_messages %>
        <div class="login-form-inner">
          <%= yield %>
        </div>
      </div>
    </div>
  </body>
</html>
#!/usr/bin/env ruby
# frozen_string_literal: true
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.

ENGINE_ROOT = File.expand_path("../..", __FILE__)
ENGINE_PATH = File.expand_path("../../lib/decidim/admin/engine", __FILE__)

# Set up gems listed in the Gemfile.
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])

require "rails/all"
require "rails/engine/commands"
# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks

# The "main" locale.
base_locale: en
## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
# locales: [es, fr]
## Reporting locale, default: en. Available: en, ru.
# internal_locale: en

# Read and write translations.
data:
  ## Translations are read from the file system. Supported format: YAML, JSON.
  ## Provide a custom adapter:
  # adapter: I18n::Tasks::Data::FileSystem

  # Locale files or `File.find` patterns where translations are read from:
  read:
    ## Default:
    # - config/locales/%{locale}.yml
    ## More files:
    # - config/locales/**/*.%{locale}.yml
    ## Another gem (replace %#= with %=):
    # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"

  # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
  # `i18n-tasks normalize -p` will force move the keys according to these rules
  write:
    ## For example, write devise and simple form keys to their respective files:
    # - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
    ## Catch-all default:
    # - config/locales/%{locale}.yml

  ## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
  # router: convervative_router

  yaml:
    write:
      # do not wrap lines at 80 characters
      line_width: -1

  ## Pretty-print JSON:
  # json:
  #   write:
  #     indent: '  '
  #     space: ' '
  #     object_nl: "\n"
  #     array_nl: "\n"

# Find translate calls
search:
  ## Paths or `File.find` patterns to search in:
  # paths:
  #  - app/

  ## Root directories for relative keys resolution.
  # relative_roots:
  #   - app/controllers
  #   - app/helpers
  #   - app/mailers
  #   - app/presenters
  #   - app/views

  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
  ##   %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
  exclude:
    - app/assets/images
    - app/assets/fonts

  ## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
  ## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
  # only: ["*.rb", "*.html.slim"]

  ## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
  # strict: true

  ## Multiple scanners can be used. Their results are merged.
  ## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
  ## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example

## Google Translate
# translation:
#   # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
#   api_key: "AbC-dEf5"

## Do not consider these keys missing:
# ignore_missing:
# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
# - '{devise,simple_form}.*'

## Consider these keys used:
# ignore_unused:
# - 'activerecord.attributes.*'
# - '{devise,kaminari,will_paginate}.*'
# - 'simple_form.{yes,no}'
# - 'simple_form.{placeholders,hints,labels}.*'
# - 'simple_form.{error_notification,required}.:'

## Exclude these keys from the `i18n-tasks eq-base' report:
# ignore_eq_base:
#   all:
#     - common.ok
#   fr,es:
#     - common.brand

## Ignore these keys completely:
# ignore:
#  - kaminari.*

## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
## e.g. in case of a relative key defined in a helper method.
## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
#
# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
#        only: %w(*.html.haml *.html.slim),
#        patterns: [['= title\b', '.page_title']] %>
#
# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
#
# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
#        patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
---
en:
  decidim:
    admin:
      menu:
        dashboard: Dashboard
      titles:
        dashboard: Dashboard
# frozen_string_literal: true
Decidim::Admin::Engine.routes.draw do
  root to: "dashboard#show", constraints: ->(request) { Admin::OrganizationDashboardConstraint.new(request).matches? }
end
# frozen_string_literal: true
$LOAD_PATH.push File.expand_path("../lib", __FILE__)

# Maintain your gem's version:
require_relative "../decidim-core/lib/decidim/core/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "decidim-admin"
  s.version     = Decidim.version
  s.authors     = ["Josep Jaume Rey Peroy", "Marc Riera Casals", "Oriol Gual Oliva"]
  s.email       = ["josepjaume@gmail.com", "mrc2407@gmail.com", "oriolgual@gmail.com"]
  s.homepage    = ""
  s.summary     = "Organization administration"
  s.description = "Organization administration to manage a single organization."
  s.license     = "MIT"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]

  s.add_dependency "decidim-core"
  s.add_dependency "rails", Decidim.rails_version
  s.add_dependency "devise", "~> 4.2"
  s.add_dependency "rectify", "~> 0.6"
  s.add_dependency "devise_invitable", "~> 1.7.0"
  s.add_dependency "foundation-rails", "~> 6.2.3.0"
  s.add_dependency "sass-rails", "~> 5.0.0"
  s.add_dependency "jquery-rails", "~> 4.0"
  s.add_dependency "turbolinks", Decidim.rails_version
  s.add_dependency "jquery-turbolinks", "~> 2.1.0"
  s.add_dependency "jbuilder", "~> 2.5"
  s.add_dependency "foundation_rails_helper", "~> 2.0.0"
  s.add_dependency "active_link_to", "~> 1.0.0"

  s.add_development_dependency "factory_girl_rails"
  s.add_development_dependency "database_cleaner", "~> 1.5.0"
  s.add_development_dependency "capybara", "~> 2.4"
  s.add_development_dependency "rspec-rails", "~> 3.5"
  s.add_development_dependency "byebug"
  s.add_development_dependency "wisper-rspec"
  s.add_development_dependency "pg"
  s.add_development_dependency "listen"
  s.add_development_dependency "launchy"
  s.add_development_dependency "i18n-tasks", "~> 0.9.5"
end
# frozen_string_literal: true
require "decidim/admin/engine"

module Decidim
  # This module contains all the logic related to a admin-wide
  # administration panel. The scope of the domain is to be able
  # to manage Organizations (tenants), as well as have a bird's
  # eye view of the whole admin.
  #
  module Admin
  end
end
# frozen_string_literal: true
require "rails"
require "active_support/all"

require "decidim/core"
require "devise"
require "jquery-rails"
require "sass-rails"
require "turbolinks"
require "jquery-turbolinks"
require "foundation-rails"
require "foundation_rails_helper"
require "jbuilder"
require "rectify"

module Decidim
  module Admin
    # Decidim's core Rails Engine.
    class Engine < ::Rails::Engine
      isolate_namespace Decidim::Admin

      config.to_prepare do
        Rails.application.config.assets.precompile += %w(
          decidim/admin.js
          decidim/admin.css
        )
      end
    end
  end
end
# frozen_string_literal: true
require "rails/generators"
require_relative "../../../../lib/generators/decidim/app_generator"

module Decidim
  module Admin
    module Generators
      # Generates a dummy test Rails app for the given library folder. It uses
      # the `AppGenerator` class to actually generate the Rails app, so this
      # generator only sets the path and some flags.
      #
      # The Rails app will be installed with some flags to disable git, tests,
      # Gemfile and other options. Refer to the `create_dummy_app` method to see
      # all the flags passed to the `AppGenerator` class, which is the one that
      # actually generates the Rails app.
      #
      # Remember that, for how generators work, actions are executed based on the
      # definition order of the public methods.
      class DummyGenerator < Rails::Generators::Base
        desc "Generate dummy app for testing purposes"

        class_option :lib_name, type: :string,
                                desc: "The library where the dummy app will be installed"

        class_option :migrate, type: :boolean, default: false,
                               desc: "Run migrations after installing decidim"

        def cleanup
          remove_directory_if_exists(dummy_path)
        end

        def create_dummy_app
          Decidim::Generators::AppGenerator.start [
            dummy_path,
            "--skip_gemfile",
            "--skip-bundle",
            "--skip-git",
            "--skip-keeps",
            "--skip-test",
            "--migrate=#{options[:migrate]}"
          ]
        end

        private

        def dummy_path
          ENV["DUMMY_PATH"] || "spec/#{short_lib_name}_dummy"
        end

        def remove_directory_if_exists(path)
          remove_dir(path) if File.directory?(path)
        end

        def short_lib_name
          options[:lib_name].split("/").last
        end
      end
    end
  end
end
# frozen_string_literal: true
# desc "Explaining what the task does"
# task :decidim_admin do
#   # Task goes here
# end
require_relative "../../decidim-core/spec/factories"
# frozen_string_literal: true
require "spec_helper"

describe "Admin dashboard access control", type: :feature do
  let(:organization) { create(:organization) }

  before do
    login_as user, scope: :user
    switch_to_host(organization.host)
  end

  context "a regular user" do
    let(:user) { create(:user, organization: organization) }

    it "can't access the admin dashboard" do
      visit decidim_admin.root_path
      expect(page.status_code).to eq(404)
    end
  end

  context "an organization admin" do
    let(:user) { create(:user, :admin, organization: organization) }

    it "can access the admin dashboard" do
      visit decidim_admin.root_path
      expect(page.status_code).to eq(200)
      expect(page).to have_content("Dashboard")
    end
  end

  context "an admin from another organization" do
    let(:other_organization) { create(:organization) }
    let(:user) { create(:user, :admin, organization: other_organization) }

    it "can't access the admin dashboard" do
      visit decidim_admin.root_path
      expect(page.status_code).to eq(404)
    end
  end
end
# frozen_string_literal: true
require "spec_helper"

describe "Admin invite", type: :feature do
  let(:form) do
    Decidim::System::RegisterOrganizationForm.new(params)
  end

  let(:params) do
    {
      name: "Gotham City",
      host: "decide.lvh.me",
      organization_admin_email: "f.laguardia@gotham.gov"
    }
  end

  before(:each) do
    Decidim::System::RegisterOrganization.new(form).call
    switch_to_host("decide.lvh.me")
  end

  describe "Accept an invitation" do
    let(:email_link) do
      last_delivery = ActionMailer::Base.deliveries.last.body.encoded
      URI.extract(last_delivery).last
    end

    it "asks for a password and redirects to the organization dashboard" do
      visit email_link

      fill_in :user_password, with: "123456"
      fill_in :user_password_confirmation, with: "123456"
      find("*[type=submit]").click

      expect(page).to have_content("Dashboard")
    end
  end
end
# frozen_string_literal: true
require 'i18n/tasks'

RSpec.describe 'I18n' do
  let(:i18n) { I18n::Tasks::BaseTask.new }
  let(:missing_keys) { i18n.missing_keys }
  let(:unused_keys) { i18n.unused_keys }

  it 'does not have missing keys' do
    expect(missing_keys).to be_empty,
      "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
  end

  it 'does not have unused keys' do
    expect(unused_keys).to be_empty,
      "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
  end
end
# frozen_string_literal: true
ENV["RAILS_ENV"] ||= "test"

if ENV["CI"]
  require "simplecov"
  SimpleCov.start

  require "codecov"
  SimpleCov.formatter = SimpleCov::Formatter::Codecov
end

begin
  require File.expand_path("../admin_dummy/config/environment", __FILE__)
rescue LoadError
  puts "Could not load dummy application. Please ensure you have run `bundle exec rake test_app`"
  exit
end

require "rspec/rails"
require "factory_girl_rails"
require "database_cleaner"
require "byebug"

# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

RSpec.configure do |config|
  config.color = true
  config.fail_fast = ENV["FAIL_FAST"] || false
  config.infer_spec_type_from_file_location!
  config.mock_with :rspec
  config.raise_errors_for_deprecations!

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, comment the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = false
end
RSpec.configure do |config|
  config.before(:each) do
    ActionMailer::Base.deliveries.clear
  end
end
def switch_to_host(host = "lvh.me")
  Capybara.app_host = "http://#{host}"
end

def switch_to_default_host
  switch_to_host nil
end

Capybara.configure do |config|
  config.always_include_port = true
end

RSpec.configure do |config|
  config.before :each, type: :feature do
    switch_to_default_host
  end
end
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end
end
RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end
RSpec.configure do |config|
  config.include Warden::Test::Helpers, type: :feature

  config.after :each, type: :feature do
    Warden.test_reset!
  end
end
require 'wisper/rspec/matchers'

RSpec::configure do |config|
  config.include(Wisper::RSpec::BroadcastMatcher)
end
gemspec path: "../decidim-admin"
PATH
  remote: ../decidim-admin
  specs:
    decidim-admin (0.0.1.alpha2)
      active_link_to (~> 1.0.0)
      decidim-core
      devise (~> 4.2)
      devise_invitable (~> 1.7.0)
      foundation-rails (~> 6.2.3.0)
      foundation_rails_helper (~> 2.0.0)
      jbuilder (~> 2.5)
      jquery-rails (~> 4.0)
      jquery-turbolinks (~> 2.1.0)
      rails (~> 5.0.0)
      rectify (~> 0.6)
      sass-rails (~> 5.0.0)
      turbolinks (~> 5.0.0)

      decidim-admin (= 0.0.1.alpha2)
  decidim-admin!
# frozen_string_literal: true
module Decidim
  module Devise
    # This controller customizes the behaviour of Devise::Invitiable.
    class InvitationsController < ::Devise::InvitationsController
      # We don't users to create invitations, so we just redirect them to the
      # homepage.
      def authenticate_inviter!
        redirect_to root_path
      end

      # Overwrite the method that returns the path after a user accepts an
      # invitation. Using the param `invite_redirect` we can redirect the user
      # to a custom path after it has accepted the invitation.
      def after_accept_path_for(resource)
        params[:invite_redirect] || after_sign_in_path_for(resource)
      end
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Devise
    # A controller so admins can recover their passwords.
    class PasswordsController < Devise::PasswordsController
    end
  end
end
# frozen_string_literal: true
module Decidim
  module Devise
    # A controller so admins can log-in and use their backend.
    class SessionsController < ::Devise::SessionsController
    end
  end
end
# frozen_string_literal: true
module Decidim
  # A custom mailer for Devise so we can tweak the invitation instructions for
  # each role.
  class DecidimDeviseMailer < Devise::Mailer
    def invitation_instructions(record, token, opts = {})
      @token = token
      devise_mail(record, record.invitation_instructions || :invitation_instructions, opts)
    end
  end
end
# frozen_string_literal: true
module Decidim
  # A middleware that enhances the request with the current organization based
  # on the hostname.
  class CurrentOrganization
    # Initializes the Rack Middleware.
    #
    # app - The Rack application
    def initialize(app)
      @app = app
    end

    # Main entry point for a Rack Middleware.
    #
    # env - A Hash.
    def call(env)
      env["decidim.current_organization"] = detect_current_organization(env)
      @app.call(env)
    end

    private

    def detect_current_organization(env)
      host = Rack::Request.new(env).host.downcase
      Decidim::Organization.where(host: host).first
    end
  end
end
require_dependency "devise/models/decidim_validatable"

           :recoverable, :rememberable, :trackable, :decidim_validatable
    validate :all_roles_are_valid

    # Public: Allows customizing the invitation instruction email content when
    # inviting a user.
    #
    # Returns a String.
    attr_accessor :invitation_instructions

    private

    def all_roles_are_valid
      errors.add(:roles, :invalid) unless roles.all? { |role| ROLES.include?(role) }
    end
<h2><%= t 'devise.invitations.edit.header' %></h2>

<%= form_for resource, as: resource_name, url: invitation_path(resource_name, invite_redirect: params[:invite_redirect]), html: { method: :put } do |f| %>
  <%= devise_error_messages! %>
  <%= f.hidden_field :invitation_token %>

  <% if f.object.class.require_password_on_accepting %>
  <p><%= f.label :password %><br />
  <%= f.password_field :password %></p>

  <p><%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %></p>
  <% end %>

  <p><%= f.submit t("devise.invitations.edit.submit_button") %></p>
<% end %>
<h2>Change your password</h2>

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>
  <%= f.hidden_field :reset_password_token %>

  <div class="field">
    <% if @minimum_password_length %>
      <em>(<%= @minimum_password_length %> characters minimum)</em><br />
    <% end %>
    <%= f.password_field :password, autofocus: true, autocomplete: "off" %>
  </div>

  <div class="field">
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
  </div>

  <div class="actions">
    <%= f.submit "Change my password" %>
  </div>
<% end %>

<%= render "decidim/devise/shared/links" %>
<h2>Forgot your password?</h2>

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.email_field :email, autofocus: true %>
  </div>

  <div class="actions">
    <%= f.submit "Send me reset password instructions" %>
  </div>
<% end %>

<%= render "decidim/devise/shared/links" %>
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div class="field">
    <%= f.email_field :email, autofocus: true %>
  </div>

  <div class="field">
    <%= f.password_field :password, autocomplete: "off" %>
  </div>

  <% if devise_mapping.rememberable? -%>
    <div class="field">
      <%= f.check_box :remember_me %>
    </div>
  <% end -%>

  <div class="actions">
    <%= f.submit "Log in" %>
  </div>
<% end %>

<%= render "decidim/devise/shared/links" %>
<%- if controller_name != 'sessions' %>
  <%= link_to "Log in", new_session_path(resource_name) %><br />
<% end -%>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end -%>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
  <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end -%>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end -%>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>

<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
  <% end -%>
<% end -%>
<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token, invite_redirect: decidim_admin.root_path, host: @resource.organization.host) %></p>
<%= accept_invitation_url(@resource, invitation_token: @token, invite_redirect: decidim_admin.root_path, host: @resource.organization.host) %>
  config.mailer = "Decidim::DecidimDeviseMailer"
  config.allow_insecure_sign_in_after_accept = true

      initializer "decidim.middleware" do |app|
        app.config.middleware.use Decidim::CurrentOrganization
      end
# frozen_string_literal: true
module Devise
  module Models
    # Validatable creates all needed validations for a user email and password.
    # Automatically validate if the email is present, unique and its format is
    # valid. Also tests presence of password, confirmation and length.
    #
    # == Options
    #
    # Validatable adds the following options to devise_for:
    #
    #   * +email_regexp+: the regular expression used to validate e-mails;
    #   * +password_length+: a range expressing password length. Defaults to 8..72.
    #
    module DecidimValidatable
      # All validations used by this module.
      VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
                     :validates_confirmation_of, :validates_length_of].freeze

      def self.required_fields(_klass)
        []
      end

      def self.included(base)
        base.extend ClassMethods
        assert_validations_api!(base)

        base.class_eval do
          validates_presence_of   :email, if: :email_required?
          validates_uniqueness_of :email, allow_blank: true, if: :email_changed?, scope: :organization
          validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?

          validates_presence_of     :password, if: :password_required?
          validates_confirmation_of :password, if: :password_required?
          validates_length_of       :password, within: password_length, allow_blank: true
        end
      end

      def self.assert_validations_api!(base) #:nodoc:
        unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }

        unless unavailable_validations.empty?
          raise "Could not use :validatable module since #{base} does not respond " \
                "to the following methods: #{unavailable_validations.to_sentence}."
        end
      end

      protected

      # Checks whether a password is needed or not. For validations only.
      # Passwords are always required if it's a new record, or if the password
      # or confirmation are being set somewhere.
      def password_required?
        !persisted? || !password.nil? || !password_confirmation.nil?
      end

      def email_required?
        true
      end

      # Methods to be injected at class level.
      module ClassMethods
        Devise::Models.config(self, :email_regexp, :password_length)
      end
    end
  end
end
        route "mount Decidim::Admin::Engine => '/admin'"
        unless ENV["CI"]
          rake "db:environment:set RAILS_ENV=development"
          rake "db:drop:all"
        end
require "spec_helper"

module Decidim
  describe CurrentOrganization do
    let(:app) { ->(env) { [200, env, "app"] } }
    let(:env) { Rack::MockRequest.env_for("https://#{host}", {}) }
    let(:host) { "city.domain.org" }
    let(:middleware) { described_class.new(app) }

    context "when an organization exists for the current host" do
      let!(:organization) { create(:organization, host: host) }

      it "sets the organization" do
        _code, new_env = middleware.call(env)

        expect(new_env["decidim.current_organization"]).to eq(organization)
      end
    end

    context "when no orgazanization exists for the current host" do
      let!(:organization) { create(:organization, host: 'fake.host.com') }

      it "doesn't set the organization" do
        _code, new_env = middleware.call(env)

        expect(new_env["decidim.current_organization"]).to be_nil
      end
    end
  end
end
  context "with roles" do
    let(:user) { build(:user, :admin) }

    it "is still valid" do
      expect(user).to be_valid
    end

    context "with an invalid role" do
      let(:user) { build(:user, roles: ["foo"]) }

      it "is not valid" do
        expect(user).to_not be_valid
      end
    end
  end

  describe "validation scopes" do
    context "when a user with the same email exists in another organization" do
      let(:email) { "foo@bar.com" }
      let(:user) { build(:user, email: email) }

      before do
        create(:user, email: email)
      end

      it "is valid" do
        expect(user).to be_valid
      end
    end
  end

      decidim-admin (= 0.0.1.alpha2)
    decidim-admin (0.0.1.alpha2)
      active_link_to (~> 1.0.0)
      decidim-core
      devise (~> 4.2)
      devise_invitable (~> 1.7.0)
      foundation-rails (~> 6.2.3.0)
      foundation_rails_helper (~> 2.0.0)
      jbuilder (~> 2.5)
      jquery-rails (~> 4.0)
      jquery-turbolinks (~> 2.1.0)
      rails (~> 5.0.0)
      rectify (~> 0.6)
      sass-rails (~> 5.0.0)
      turbolinks (~> 5.0.0)
        ) do |invitable|
          invitable.invitation_instructions = :organization_admin_invitation_instructions
        end

          it "sends a custom email" do
            expect { command.call }.to change { ActionMailer::Base.deliveries.count }.by(1)

            last_delivery_body = ActionMailer::Base.deliveries.last.body.encoded
            expect(last_delivery_body).to include(URI.encode_www_form(["/admin"]))
          end
RSpec.configure do |config|
  config.before(:each) do
    ActionMailer::Base.deliveries.clear
  end
end
  spec.add_dependency "decidim-admin", Decidim.version
require "decidim/admin"