diff --git a/Gemfile b/Gemfile index 204c2310..d2c77d8e 100644 --- a/Gemfile +++ b/Gemfile @@ -29,9 +29,14 @@ gem 'jbuilder', '~> 2.5' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +gem 'email_address' +gem 'rails_warden' +gem 'haml-rails' +gem 'bootstrap', '~> 4.0.0.beta3' +gem 'jekyll' + group :development, :test do - # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'pry' # Adds support for Capybara system testing and selenium driver gem 'capybara', '~> 2.13' gem 'selenium-webdriver' @@ -45,6 +50,3 @@ group :development do gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end - -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock index 3c15b682..c7c64534 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,9 +41,14 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) arel (8.0.0) + autoprefixer-rails (7.2.4) + execjs bindex (0.5.0) + bootstrap (4.0.0.beta3) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.3, < 2) + sass (>= 3.5.2) builder (3.2.3) - byebug (9.1.0) capybara (2.16.1) addressable mini_mime (>= 0.1.3) @@ -53,18 +58,63 @@ GEM xpath (~> 2.0) childprocess (0.8.0) ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) + colorator (1.1.0) concurrent-ruby (1.0.5) crass (1.0.3) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + email_address (0.1.6) + netaddr + simpleidn erubi (1.7.0) + erubis (2.7.0) + eventmachine (1.2.5) execjs (2.7.0) ffi (1.9.18) + forwardable-extended (2.6.0) globalid (0.4.1) activesupport (>= 4.2.0) + haml (5.0.4) + temple (>= 0.8.0) + tilt + haml-rails (1.0.0) + actionpack (>= 4.0.1) + activesupport (>= 4.0.1) + haml (>= 4.0.6, < 6.0) + html2haml (>= 1.0.1) + railties (>= 4.0.1) + html2haml (2.2.0) + erubis (~> 2.7.0) + haml (>= 4.0, < 6) + nokogiri (>= 1.6.0) + ruby_parser (~> 3.5) + http_parser.rb (0.6.0) i18n (0.9.1) concurrent-ruby (~> 1.0) jbuilder (2.7.0) activesupport (>= 4.2.0) multi_json (>= 1.2) + jekyll (3.7.0) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (~> 1.14) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-sass-converter (1.5.1) + sass (~> 3.4) + jekyll-watch (2.0.0) + listen (~> 3.0) + kramdown (1.16.2) + liquid (4.0.0) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -74,14 +124,22 @@ GEM nokogiri (>= 1.5.9) mail (2.7.0) mini_mime (>= 0.1.1) + mercenary (0.3.6) method_source (0.9.0) mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.10.3) multi_json (1.12.2) + netaddr (1.5.1) nio4r (2.1.0) nokogiri (1.8.1) mini_portile2 (~> 2.3.0) + pathutil (0.16.1) + forwardable-extended (~> 2.6) + popper_js (1.12.9) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) public_suffix (3.0.1) puma (3.11.0) rack (2.0.3) @@ -104,6 +162,8 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) + rails_warden (0.5.8) + warden (>= 1.0.0) railties (5.1.4) actionpack (= 5.1.4) activesupport (= 5.1.4) @@ -114,8 +174,12 @@ GEM rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) + rouge (3.1.0) ruby_dep (1.5.0) + ruby_parser (3.10.1) + sexp_processor (~> 4.9) rubyzip (1.2.1) + safe_yaml (1.0.4) sass (3.5.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) @@ -130,6 +194,8 @@ GEM selenium-webdriver (3.8.0) childprocess (~> 0.5) rubyzip (~> 1.0) + sexp_processor (4.10.0) + simpleidn (0.0.9) spring (2.0.2) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -143,6 +209,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.13) + temple (0.8.0) thor (0.20.0) thread_safe (0.3.6) tilt (2.0.8) @@ -153,6 +220,8 @@ GEM thread_safe (~> 0.1) uglifier (4.1.2) execjs (>= 0.3.0, < 3) + warden (1.2.7) + rack (>= 1.0) web-console (3.5.1) actionview (>= 5.0) activemodel (>= 5.0) @@ -168,19 +237,23 @@ PLATFORMS ruby DEPENDENCIES - byebug + bootstrap (~> 4.0.0.beta3) capybara (~> 2.13) + email_address + haml-rails jbuilder (~> 2.5) + jekyll listen (>= 3.0.5, < 3.2) + pry puma (~> 3.7) rails (~> 5.1.4) + rails_warden sass-rails (~> 5.0) selenium-webdriver spring spring-watcher-listen (~> 2.0.0) sqlite3 turbolinks (~> 5) - tzinfo-data uglifier (>= 1.3.0) web-console (>= 3.3.0) diff --git a/app/assets/images/background.jpg b/app/assets/images/background.jpg new file mode 100644 index 00000000..0e6c2d0d Binary files /dev/null and b/app/assets/images/background.jpg differ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index d05ea0f5..00000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's - * vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS - * files in this directory. Styles in this file should be added after the last require_* statement. - * It is generally better to create a new file per style scope. - * - *= require_tree . - *= require_self - */ diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 00000000..eead430f --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,38 @@ +@import "bootstrap"; + +$footer-height: 60px; + +.background-cover { + background: image-url("background.jpg") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +.full-height { + height: calc(100vh - #{$footer-height}); +} + +html { + position: relative; + min-height: 100%; +} + +body { + margin-bottom: $footer-height; +} + +.footer { + position: absolute; + bottom: 0; + width: 100%; + height: $footer-height; + line-height: $footer-height; + text-align: center; +} + +textarea.post-content { + min-height: 80vh; + font-family: monospace; +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1c07694e..6c68715a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception + + def index + redirect_to sites_path + end end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb new file mode 100644 index 00000000..b8978a98 --- /dev/null +++ b/app/controllers/login_controller.rb @@ -0,0 +1,21 @@ +class LoginController < ApplicationController + protect_from_forgery with: :exception + + def index + redirect_to new_login_path + end + + def new + @has_cover = true + render 'login/new' + end + + def create + authenticate + + if authenticated? + # TODO enviar a la URL de donde vinimos + redirect_to sites_path + end + end +end diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb new file mode 100644 index 00000000..3de9b89e --- /dev/null +++ b/app/controllers/sites_controller.rb @@ -0,0 +1,6 @@ +class SitesController < ApplicationController + before_action :authenticate! + + def index + end +end diff --git a/app/models/usuaria.rb b/app/models/usuaria.rb new file mode 100644 index 00000000..15376cdc --- /dev/null +++ b/app/models/usuaria.rb @@ -0,0 +1,18 @@ +# Un modelo para la usuaria que no se corresponde con una base de datos +# porque vienen de IMAP +# +# Una usuaria puede tener muchos sitios, pero no podemos establecer +# relaciones estilo has_many porque no estamos usando ActiveRecord, que +# requeriría que guardemos todo en una base de datos. +class Usuaria < OpenStruct + # Crear una usuaria ad hoc, porque la base de datos de usuarias está + # en otro lado. + def self.find(username) + Usuaria.new(username: username) + end + + # Obtener todos los sitios de esta usuaria + def sites + @sites ||= Site.all_for(self) + end +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb deleted file mode 100644 index 3e476b10..00000000 --- a/app/views/layouts/application.html.erb +++ /dev/null @@ -1,14 +0,0 @@ - - - - Sutty - <%= csrf_meta_tags %> - - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - - - - <%= yield %> - - diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml new file mode 100644 index 00000000..be34424a --- /dev/null +++ b/app/views/layouts/application.html.haml @@ -0,0 +1,15 @@ +!!! +%html + %head + %meta{content: "text/html; charset=UTF-8", 'http-equiv': "Content-Type"}/ + %title Sutty + = csrf_meta_tags + = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' + %body{class: @has_cover ? 'background-cover' : ''} + .container-fluid + = yield + %footer.footer + %p{style: @has_cover ? 'color: white' : ''} + %a{href: 'https://0xacab.org/itacate-kefir/sutty'} Sutty + es desarrollada por + %a{href: 'https://kefir.red'} Kéfir diff --git a/app/views/login/new.haml b/app/views/login/new.haml new file mode 100644 index 00000000..a61340e4 --- /dev/null +++ b/app/views/login/new.haml @@ -0,0 +1,14 @@ +.row.align-items-center.justify-content-center.full-height + .col-md-6.align-self-center + - if flash[:error] + .alert.alert-danger{role: 'alert'} + = flash[:error] + + = form_tag login_path do + .form-group + %input{type: 'email', name: 'username', class: 'form-control', placeholder: t('login.email')} + .form-group + %input{type: 'password', name: 'password', class: 'form-control', placeholder: t('login.password')} + + .form-group + %input{type: 'submit', value: t('login.submit', class: 'btn btn-lg btn-primary btn-block'} diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml new file mode 100644 index 00000000..83d23fb4 --- /dev/null +++ b/app/views/sites/index.haml @@ -0,0 +1 @@ +%h1= t('sites.title') diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb new file mode 100644 index 00000000..c750b089 --- /dev/null +++ b/config/initializers/warden.rb @@ -0,0 +1,18 @@ +require 'warden/imap' + +Rails.configuration.middleware.use RailsWarden::Manager do |manager| + manager.default_strategies :imap + manager.failure_app = -> (env) { LoginController.action(:new).call(env) } +end + +class Warden::SessionSerializer + def serialize(record) + [record.username] + end + + def deserialize(keys) + Usuaria.find(keys.first) + end +end + +Warden::Strategies.add(:imap, Warden::IMAP::Strategy) diff --git a/config/locales/en.yml b/config/locales/en.yml index decc5a85..04143fca 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,33 +1,7 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t 'hello' -# -# In views, this is aliased to just `t`: -# -# <%= t('hello') %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# The following keys must be escaped otherwise they will not be retrieved by -# the default I18n backend: -# -# true, false, on, off, yes, no -# -# Instead, surround them with single quotes. -# -# en: -# 'true': 'foo' -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - en: - hello: "Hello world" + login: + email: 'E-mail' + password: 'Password' + submit: 'Log in' + sites: + title: 'Sites' diff --git a/config/locales/es.yml b/config/locales/es.yml new file mode 100644 index 00000000..cd546852 --- /dev/null +++ b/config/locales/es.yml @@ -0,0 +1,7 @@ +es: + login: + email: 'Dirección de correo' + password: 'Contraseña' + submit: 'Ingresar' + sites: + title: 'Sitios' diff --git a/config/routes.rb b/config/routes.rb index 787824f8..a16d6e71 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,10 @@ Rails.application.routes.draw do - # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + root 'application#index' + + get 'login/new', to: 'login#new' + post 'login', to: 'login#create' + + resources :sites do + resources :posts + end end diff --git a/lib/warden/imap.rb b/lib/warden/imap.rb new file mode 100644 index 00000000..e757ca1c --- /dev/null +++ b/lib/warden/imap.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'net/imap' +require 'warden' +require 'email_address' + +module Warden + module IMAP + # Una estrategia de autenticación por IMAP + class Strategy < Warden::Strategies::Base + def valid? + return false unless params.include? 'username' + return false unless params.include? 'password' + + @email = EmailAddress.new(params['username']) + + @email.valid? + end + + def authenticate! + imap_connect + imap_login + end + + private + + def imap_connect + # No vamos a enviar la contraseña en texto plano a ningún lado + @imap = Net::IMAP.new(@email.host_name, ssl: true) + # Errores más comunes según + # https://ruby-doc.org/stdlib-2.0.0/libdoc/net/imap/rdoc/Net/IMAP.html + rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ENETUNREACH, + SocketError, Net::IMAP::ByeResponseError => e + + @imap.disconnect + fail! e.to_s + end + + def imap_login + @imap.login(@email.normal, params['password']) + @imap.disconnect + + success! Usuaria.find(@email.normal) + rescue EOFError => e + @imap.disconnect + fail! e.to_s + end + end + end +end +