ditty 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Readme.md +3 -12
- data/ditty.gemspec +4 -1
- data/exe/ditty +4 -0
- data/lib/ditty/cli.rb +44 -0
- data/lib/ditty/controllers/application.rb +3 -4
- data/lib/ditty/controllers/component.rb +38 -14
- data/lib/ditty/controllers/main.rb +115 -34
- data/lib/ditty/controllers/users.rb +7 -10
- data/lib/ditty/db.rb +6 -1
- data/lib/ditty/emails/base.rb +74 -0
- data/lib/ditty/emails/forgot_password.rb +15 -0
- data/lib/ditty/helpers/authentication.rb +2 -2
- data/lib/ditty/helpers/component.rb +7 -4
- data/lib/ditty/helpers/views.rb +5 -2
- data/lib/ditty/listener.rb +41 -7
- data/lib/ditty/models/user.rb +8 -4
- data/lib/ditty/policies/identity_policy.rb +2 -2
- data/lib/ditty/rake_tasks.rb +6 -5
- data/lib/ditty/services/authentication.rb +55 -0
- data/lib/ditty/services/email.rb +18 -20
- data/lib/ditty/services/logger.rb +10 -8
- data/lib/ditty/services/pagination_wrapper.rb +82 -0
- data/lib/ditty/services/settings.rb +45 -0
- data/lib/ditty/version.rb +1 -1
- data/migrate/20180307_password_reset.rb +10 -0
- data/views/audit_logs/index.haml +1 -1
- data/views/emails/base.haml +2 -0
- data/views/emails/forgot_password.haml +26 -0
- data/views/emails/layouts/action.haml +68 -0
- data/views/emails/layouts/alert.haml +88 -0
- data/views/emails/layouts/billing.haml +108 -0
- data/views/identity/forgot.haml +16 -0
- data/views/identity/login.haml +16 -5
- data/views/identity/register.haml +8 -0
- data/views/identity/reset.haml +20 -0
- data/views/layout.haml +2 -0
- metadata +62 -5
- data/lib/ditty/helpers/wisper.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6858577e8b718519ffc9090091e2709558d0ffd52d938469cb28758bcb45317
|
4
|
+
data.tar.gz: 55d43869a651d32b3dcbcedf093a5c7ef2a01fa83335d7a36c7f3475b61d695b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5606f0ac962ce44b97b08408f6d18bcff35271b87ff4fd88af1a42f85d4f890edafaca116a438b7652fe7b52eb7407074a531c7fba7c63387fe5ff71093ccb7
|
7
|
+
data.tar.gz: ca27dfe35f0249414bf54367880bc11c199cead75a45a2fbb809e20d42d626ba734c43b0c02a82729473b8365f4ff1ec835708a29b188d5f2d1700d112a5eaec
|
data/.gitignore
CHANGED
data/Readme.md
CHANGED
@@ -32,18 +32,9 @@ gem install ditty
|
|
32
32
|
## Usage
|
33
33
|
|
34
34
|
1. Add the components to your rack config file. See the included [`config.ru`](https://github.com/EagerELK/ditty/blob/master/config.ru) file for an example setup
|
35
|
-
2.
|
36
|
-
3.
|
37
|
-
4.
|
38
|
-
|
39
|
-
```bash
|
40
|
-
bundle exec rake proxes:prep
|
41
|
-
bundle exec rake proxes:generate_tokens
|
42
|
-
bundle exec rake proxes:migrate
|
43
|
-
bundle exec rake proxes:seed
|
44
|
-
```
|
45
|
-
|
46
|
-
4. Start up the web app: `bundle exec rackup`
|
35
|
+
2. Set the DB connection as the `DATABASE_URL` ENV variable: `DATABASE_URL=sqlite://development.db`
|
36
|
+
3. Run the Ditty migrations: `bundle exec ditty migrate`
|
37
|
+
4. Run the Ditty server: `bundle exec ditty server`
|
47
38
|
|
48
39
|
## Components
|
49
40
|
|
data/ditty.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
|
18
18
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
19
|
spec.bindir = 'exe'
|
20
|
-
spec.executables =
|
20
|
+
spec.executables = ['ditty']
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
23
|
spec.add_development_dependency 'bundler', '~> 1.12'
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_dependency 'haml', '~> 5.0'
|
34
34
|
spec.add_dependency 'logger', '~> 1.0'
|
35
35
|
spec.add_dependency 'oga', '>= 2.14'
|
36
|
+
spec.add_dependency 'mail', '>= 1.7'
|
36
37
|
spec.add_dependency 'omniauth', '~> 1.0'
|
37
38
|
spec.add_dependency 'omniauth-identity', '~> 1.0'
|
38
39
|
spec.add_dependency 'pundit', '~> 1.0'
|
@@ -43,5 +44,7 @@ Gem::Specification.new do |spec|
|
|
43
44
|
spec.add_dependency 'sinatra-contrib', '~> 2.0'
|
44
45
|
spec.add_dependency 'sinatra-flash', '~> 0.3'
|
45
46
|
spec.add_dependency 'tilt', '>= 2'
|
47
|
+
spec.add_dependency 'thor', '>= 0.20'
|
48
|
+
spec.add_dependency 'will_paginate', '>= 3.1'
|
46
49
|
spec.add_dependency 'wisper', '~> 2.0'
|
47
50
|
end
|
data/exe/ditty
ADDED
data/lib/ditty/cli.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# https://nandovieira.com/creating-generators-and-executables-with-thor
|
2
|
+
require 'dotenv/load'
|
3
|
+
require 'thor'
|
4
|
+
require 'ditty'
|
5
|
+
require 'ditty/rake_tasks'
|
6
|
+
require 'rack'
|
7
|
+
require 'rake'
|
8
|
+
|
9
|
+
module Ditty
|
10
|
+
class CLI < Thor
|
11
|
+
include Thor::Actions
|
12
|
+
|
13
|
+
desc 'server', 'Start the Ditty server'
|
14
|
+
require './application' if File.exist?('application.rb')
|
15
|
+
def server
|
16
|
+
# Ensure the token files are present
|
17
|
+
Rake::Task['ditty:generate_tokens'].invoke
|
18
|
+
|
19
|
+
# Prep Ditty
|
20
|
+
Rake::Task['ditty:prep'].invoke
|
21
|
+
|
22
|
+
# Check the migrations
|
23
|
+
Rake::Task['ditty:migrate:check'].invoke
|
24
|
+
|
25
|
+
# Seed Ditty DB
|
26
|
+
puts 'Seeding the Ditty DB'
|
27
|
+
Rake::Task['ditty:seed'].invoke
|
28
|
+
|
29
|
+
# RackUP!
|
30
|
+
puts 'Starting the Ditty Server'
|
31
|
+
Rack::Server.start(config: 'config.ru')
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'migrate', 'Run the Ditty migrations'
|
35
|
+
def migrate
|
36
|
+
# Prep Ditty
|
37
|
+
Rake::Task['ditty:prep'].invoke
|
38
|
+
|
39
|
+
# Run the migrations
|
40
|
+
Rake::Task['ditty:migrate:up'].invoke
|
41
|
+
puts 'Ditty Migrations Executed'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -7,7 +7,6 @@ require 'sinatra/flash'
|
|
7
7
|
require 'sinatra/respond_with'
|
8
8
|
require 'ditty/helpers/views'
|
9
9
|
require 'ditty/helpers/pundit'
|
10
|
-
require 'ditty/helpers/wisper'
|
11
10
|
require 'ditty/helpers/authentication'
|
12
11
|
require 'ditty/services/logger'
|
13
12
|
require 'active_support'
|
@@ -23,7 +22,7 @@ module Ditty
|
|
23
22
|
set :view_location, nil
|
24
23
|
set :model_class, nil
|
25
24
|
# The order here is important, since Wisper has a deprecated method respond_with method
|
26
|
-
helpers Wisper::Publisher
|
25
|
+
helpers Wisper::Publisher
|
27
26
|
helpers Helpers::Pundit, Helpers::Views, Helpers::Authentication
|
28
27
|
|
29
28
|
register Sinatra::Flash, Sinatra::RespondWith
|
@@ -53,7 +52,7 @@ module Ditty
|
|
53
52
|
end
|
54
53
|
|
55
54
|
configure :production, :development do
|
56
|
-
|
55
|
+
disable :logging
|
57
56
|
use Rack::CommonLogger, Ditty::Services::Logger.instance
|
58
57
|
end
|
59
58
|
|
@@ -131,7 +130,7 @@ module Ditty
|
|
131
130
|
::Ditty::Services::Logger.instance.debug "Running with #{self.class}"
|
132
131
|
if request.path =~ /.*\.json\Z/
|
133
132
|
content_type :json
|
134
|
-
request.path_info = request.path_info.gsub(/.json$/,'')
|
133
|
+
request.path_info = request.path_info.gsub(/.json$/, '')
|
135
134
|
end
|
136
135
|
# Ensure the accept header is set. People forget to include it in API requests
|
137
136
|
content_type(:json) if request.accept.count.eql?(1) && request.accept.first.to_s.eql?('*/*')
|
@@ -18,18 +18,36 @@ module Ditty
|
|
18
18
|
dataset.first(settings.model_class.primary_key => id)
|
19
19
|
end
|
20
20
|
|
21
|
+
def skip_verify!
|
22
|
+
@skip_verify = true
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
return if settings.environment == 'production'
|
27
|
+
if (response.successful? || response.redirection?) && @skip_verify == false
|
28
|
+
verify_authorized if settings.environment != 'production'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
after '/' do
|
33
|
+
return if settings.environment == 'production' || request.request_method != 'GET'
|
34
|
+
if (response.successful? || response.redirection?) && @skip_verify == false
|
35
|
+
verify_policy_scoped
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
21
39
|
# List
|
22
40
|
get '/' do
|
23
41
|
authorize settings.model_class, :list
|
24
42
|
|
25
43
|
result = list
|
26
44
|
|
27
|
-
|
45
|
+
broadcast(:component_list, target: self)
|
28
46
|
list_response(result)
|
29
47
|
end
|
30
48
|
|
31
49
|
# Create Form
|
32
|
-
get '/new' do
|
50
|
+
get '/new/?' do
|
33
51
|
authorize settings.model_class, :create
|
34
52
|
|
35
53
|
entity = settings.model_class.new(permitted_attributes(settings.model_class, :create))
|
@@ -44,24 +62,26 @@ module Ditty
|
|
44
62
|
entity = settings.model_class.new(permitted_attributes(settings.model_class, :create))
|
45
63
|
authorize entity, :create
|
46
64
|
|
47
|
-
entity.
|
65
|
+
entity.db.transaction do
|
66
|
+
entity.save # Will trigger a Sequel::ValidationFailed exception if the model is incorrect
|
67
|
+
broadcast(:component_create, target: self, entity: entity)
|
68
|
+
end
|
48
69
|
|
49
|
-
log_action("#{dehumanized}_create".to_sym) if settings.track_actions
|
50
70
|
create_response(entity)
|
51
71
|
end
|
52
72
|
|
53
73
|
# Read
|
54
|
-
get '/:id' do |id|
|
74
|
+
get '/:id/?' do |id|
|
55
75
|
entity = read(id)
|
56
76
|
halt 404 unless entity
|
57
77
|
authorize entity, :read
|
58
78
|
|
59
|
-
|
79
|
+
broadcast(:component_read, target: self, entity: entity)
|
60
80
|
read_response(entity)
|
61
81
|
end
|
62
82
|
|
63
83
|
# Update Form
|
64
|
-
get '/:id/edit' do |id|
|
84
|
+
get '/:id/edit/?' do |id|
|
65
85
|
entity = read(id)
|
66
86
|
halt 404 unless entity
|
67
87
|
authorize entity, :update
|
@@ -72,26 +92,30 @@ module Ditty
|
|
72
92
|
end
|
73
93
|
|
74
94
|
# Update
|
75
|
-
put '/:id' do |id|
|
95
|
+
put '/:id/?' do |id|
|
76
96
|
entity = read(id)
|
77
97
|
halt 404 unless entity
|
78
98
|
authorize entity, :update
|
79
99
|
|
80
|
-
entity.
|
81
|
-
|
100
|
+
entity.db.transaction do
|
101
|
+
entity.set(permitted_attributes(settings.model_class, :update))
|
102
|
+
entity.save # Will trigger a Sequel::ValidationFailed exception if the model is incorrect
|
103
|
+
broadcast(:component_update, target: self, entity: entity)
|
104
|
+
end
|
82
105
|
|
83
|
-
log_action("#{dehumanized}_update".to_sym) if settings.track_actions
|
84
106
|
update_response(entity)
|
85
107
|
end
|
86
108
|
|
87
|
-
delete '/:id' do |id|
|
109
|
+
delete '/:id/?' do |id|
|
88
110
|
entity = read(id)
|
89
111
|
halt 404 unless entity
|
90
112
|
authorize entity, :delete
|
91
113
|
|
92
|
-
entity.
|
114
|
+
entity.db.transaction do
|
115
|
+
entity.destroy
|
116
|
+
broadcast(:component_delete, target: self, entity: entity)
|
117
|
+
end
|
93
118
|
|
94
|
-
log_action("#{dehumanized}_delete".to_sym) if settings.track_actions
|
95
119
|
delete_response(entity)
|
96
120
|
end
|
97
121
|
end
|
@@ -1,14 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'ditty/controllers/application'
|
4
|
+
require 'ditty/services/email'
|
5
|
+
require 'securerandom'
|
4
6
|
|
5
7
|
module Ditty
|
6
8
|
class Main < Application
|
9
|
+
set track_actions: true
|
10
|
+
|
7
11
|
def find_template(views, name, engine, &block)
|
8
12
|
super(views, name, engine, &block) # Root
|
9
13
|
super(::Ditty::App.view_folder, name, engine, &block) # Basic Plugin
|
10
14
|
end
|
11
15
|
|
16
|
+
CHECK_PATHS = [settings.map_path, "#{settings.map_path}/auth/identity"].freeze
|
17
|
+
|
18
|
+
before(/.*/) do
|
19
|
+
return unless CHECK_PATHS.include? request.path
|
20
|
+
# Redirect to the registration page if there's no SA user
|
21
|
+
sa = Role.find_or_create(name: 'super_admin')
|
22
|
+
if User.where(roles: sa).count == 0
|
23
|
+
flash[:info] = 'Please register the super admin user.'
|
24
|
+
redirect "#{settings.map_path}/auth/identity/register"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
12
28
|
# Home Page
|
13
29
|
get '/' do
|
14
30
|
authenticate!
|
@@ -18,37 +34,62 @@ module Ditty
|
|
18
34
|
# OmniAuth Identity Stuff
|
19
35
|
# Log in Page
|
20
36
|
get '/auth/identity' do
|
21
|
-
# Redirect to the registration page if there's no SA user
|
22
|
-
sa = Role.find_or_create(name: 'super_admin')
|
23
|
-
if User.where(roles: sa).count == 0
|
24
|
-
flash[:info] = 'Please register the super admin user.'
|
25
|
-
redirect "#{settings.map_path}/auth/identity/register"
|
26
|
-
end
|
27
37
|
haml :'identity/login', locals: { title: 'Log In' }
|
28
38
|
end
|
29
39
|
|
30
|
-
get '/auth/
|
31
|
-
|
32
|
-
flash[:warning] = 'Invalid credentials. Please try again.'
|
33
|
-
redirect "#{settings.map_path}/auth/identity"
|
40
|
+
get '/auth/identity/forgot' do
|
41
|
+
haml :'identity/forgot', locals: { title: 'Forgot your password?' }
|
34
42
|
end
|
35
43
|
|
36
|
-
post '/auth/identity/
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
post '/auth/identity/forgot' do
|
45
|
+
email = params['email']
|
46
|
+
identity = Identity[username: email]
|
47
|
+
if identity
|
48
|
+
# Update record
|
49
|
+
token = SecureRandom.hex(16)
|
50
|
+
identity.update(reset_token: token, reset_requested: Time.now)
|
51
|
+
# Send Email
|
52
|
+
reset_url = "#{request.base_url}#{settings.map_path}/auth/identity/reset?token=#{token}"
|
53
|
+
Ditty::Services::Email.deliver(
|
54
|
+
:forgot_password,
|
55
|
+
email,
|
56
|
+
locals: { identity: identity, reset_url: reset_url, request: request }
|
57
|
+
)
|
58
|
+
end
|
59
|
+
flash[:info] = 'An email was sent to the email provided with instructions on how to reset your password'
|
60
|
+
redirect '/auth/identity'
|
61
|
+
end
|
62
|
+
|
63
|
+
get '/auth/identity/reset' do
|
64
|
+
identity = Identity[reset_token: params['token']]
|
65
|
+
halt 404 unless identity && identity.reset_requested && identity.reset_requested > (Time.now - (24 * 60 * 60))
|
66
|
+
|
67
|
+
haml :'identity/reset', locals: { title: 'Reset your password', identity: identity }
|
68
|
+
end
|
69
|
+
|
70
|
+
put '/auth/identity/reset' do
|
71
|
+
identity = Identity[reset_token: params['token']]
|
72
|
+
halt 404 unless identity && identity.reset_requested && identity.reset_requested > (Time.now - (24 * 60 * 60))
|
73
|
+
|
74
|
+
identity_params = permitted_attributes(Identity, :update)
|
75
|
+
|
76
|
+
identity.set identity_params.merge(reset_token: nil, reset_requested: nil)
|
77
|
+
if identity.valid? && identity.save
|
78
|
+
broadcast(:identity_update_password, target: self, details: "IP: #{request.ip}")
|
79
|
+
flash[:success] = 'Password Updated'
|
48
80
|
redirect "#{settings.map_path}/auth/identity"
|
81
|
+
else
|
82
|
+
broadcast(:identity_update_password_failed, target: self, details: "IP: #{request.ip}")
|
83
|
+
haml :'identity/reset', locals: { title: 'Reset your password', identity: identity }
|
49
84
|
end
|
50
85
|
end
|
51
86
|
|
87
|
+
get '/auth/failure' do
|
88
|
+
broadcast(:user_failed_login, target: self, details: "IP: #{request.ip}")
|
89
|
+
flash[:warning] = 'Invalid credentials. Please try again.'
|
90
|
+
redirect "#{settings.map_path}/auth/identity"
|
91
|
+
end
|
92
|
+
|
52
93
|
# Register Page
|
53
94
|
get '/auth/identity/register' do
|
54
95
|
authorize ::Ditty::Identity, :register
|
@@ -62,18 +103,20 @@ module Ditty
|
|
62
103
|
authorize ::Ditty::Identity, :register
|
63
104
|
|
64
105
|
identity = Identity.new(params['identity'])
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
user.add_role sa if User.where(roles: sa).count == 0
|
106
|
+
begin
|
107
|
+
DB.transaction do
|
108
|
+
identity.save # Will trigger a Sequel::ValidationFailed exception if the model is incorrect
|
109
|
+
user = User.find(email: identity.username)
|
110
|
+
if user.nil?
|
111
|
+
user = User.create(email: identity.username)
|
72
112
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
113
|
+
broadcast(:user_register, target: self, values: { user: user }, details: "IP: #{request.ip}")
|
114
|
+
end
|
115
|
+
user.add_identity identity
|
116
|
+
flash[:info] = 'Successfully Registered. Please log in'
|
117
|
+
redirect "#{settings.map_path}/auth/identity"
|
118
|
+
end
|
119
|
+
rescue Sequel::ValidationFailed
|
77
120
|
flash.now[:warning] = 'Could not complete the registration. Please try again.'
|
78
121
|
haml :'identity/register', locals: { identity: identity }
|
79
122
|
end
|
@@ -81,13 +124,51 @@ module Ditty
|
|
81
124
|
|
82
125
|
# Logout Action
|
83
126
|
delete '/auth/identity' do
|
84
|
-
|
127
|
+
broadcast(:user_logout, target: self, details: "IP: #{request.ip}")
|
85
128
|
logout
|
86
129
|
flash[:info] = 'Logged Out'
|
87
130
|
|
88
131
|
redirect "#{settings.map_path}/"
|
89
132
|
end
|
90
133
|
|
134
|
+
post '/auth/identity/callback' do
|
135
|
+
if env['omniauth.auth']
|
136
|
+
# Successful Login
|
137
|
+
user = User.find(email: env['omniauth.auth']['info']['email'])
|
138
|
+
self.current_user = user
|
139
|
+
broadcast(:user_login, target: self, details: "IP: #{request.ip}")
|
140
|
+
flash[:success] = 'Logged In'
|
141
|
+
redirect env['omniauth.origin'] || "#{settings.map_path}/"
|
142
|
+
else
|
143
|
+
# Failed Login
|
144
|
+
broadcast(:identity_failed_login, target: self, details: "IP: #{request.ip}")
|
145
|
+
flash[:warning] = 'Invalid credentials. Please try again.'
|
146
|
+
redirect "#{settings.map_path}/auth/identity"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
get '/auth/:provider/callback' do
|
151
|
+
if env['omniauth.auth']
|
152
|
+
# Successful Login
|
153
|
+
user = User.find(email: env['omniauth.auth']['info']['email'])
|
154
|
+
if user.nil?
|
155
|
+
DB.transaction do
|
156
|
+
user = User.create(email: env['omniauth.auth']['info']['email'])
|
157
|
+
broadcast(:user_register, target: self, values: { user: user }, details: "IP: #{request.ip}")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
self.current_user = user
|
161
|
+
broadcast(:user_login, target: self, details: "IP: #{request.ip}")
|
162
|
+
flash[:success] = 'Logged In'
|
163
|
+
redirect env['omniauth.origin'] || "#{settings.map_path}/"
|
164
|
+
else
|
165
|
+
# Failed Login
|
166
|
+
broadcast(:user_failed_login, target: self, details: "IP: #{request.ip}")
|
167
|
+
flash[:warning] = 'Invalid credentials. Please try again.'
|
168
|
+
redirect "#{settings.map_path}/auth/identity"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
91
172
|
# Unauthenticated
|
92
173
|
get '/unauthenticated' do
|
93
174
|
redirect "#{settings.map_path}/auth/identity"
|