authlane 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5d433f29ea95649c1f3c82327cdf25585028d774
4
+ data.tar.gz: 4a2e2944a3a75b5d263ecbf74cbc55e3327df5e0
5
+ SHA512:
6
+ metadata.gz: 070797815dead62c30f657efc56c5dfb3abdf8f0c792d68062469dcb67de9480907b204391a62b2a48bcf85e08f18e017b290fda9787bc0b3f6bd29bf762a280
7
+ data.tar.gz: f2191d60b1b6f4e4f6acdf7f393c9e9bbf375a7214edd0ff986f2183f4f7f513c9bb4e8508ddb73355b6491007c9ac41528a77dcbdc80e86b93bb02e0b12417c
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1 @@
1
+ --no-private --main=README.md --markup=markdown lib/**/*.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gembootstrap.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014-2015 Patrick Lam
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # AuthLane
2
+
3
+ The **AuthLane** Sinatra Extension allows easy User authentication with support for different User *roles* and automatic login via Cookies. It exposes Helper methods to tell which routes are protected or involved in the authentication process.
4
+
5
+ The actual authentication logic (*strategy*) is defined by the Application using a namespaced DSL provided by this extension, while the general Extension configuration is handled with Sinatra's `set` method, which will be described in more detail below.
6
+
7
+ # Configuring *AuthLane*
8
+
9
+ **AuthLane**'s configuration data is available under Sinatra's `settings` object with the key `:authlane` as a Hash, so changing config values is simply done with Sinatra's `set` method.
10
+
11
+ ```
12
+ set :authlane, :failed_route => '/login'
13
+ ```
14
+
15
+ The following settings can be customize (the used values are their defaults):
16
+
17
+ ```
18
+ set :authlane, :failed_route => '/user/unauthorized',
19
+ :session_key => :authlane,
20
+ :remember_cookie => :authlane_token,
21
+ :serialize_user => [:id]
22
+ ```
23
+
24
+ ### `:failed_route`
25
+
26
+ The `:failed_route` sets the route String, where AuthLane should redirect to in case a route requires authorisation and the User ist not logged in. It typically is the route to display the login form, but can be set to anything that is needed, as long the it is not protected by authorisation as well.
27
+
28
+ ### `:seession_key`
29
+
30
+ The `:session_key` sets the name (as a Symbol) of the Session variable where User credentials of a logged in User are stored. The stored User data are wrapped inside a `SerializedUser` object and can be retrieved by using Sinatra's `session` helper and giving it the key that is defined here `session[:authlane]`. Alternatively, the AuthLane Helper exposes the method `current_user` to provide easy access to User data.
31
+
32
+ ### `:remember_cookie`
33
+
34
+ Customize the Cookie's name that stores the token hash used for the *Remember Me* functionality. The setting (and creation) of the token needs to be implemented by the Extension user in both the Auth and Remember Strategy.
35
+
36
+ ### `:serialize_user`
37
+
38
+ The `:serialized_user` settings contains an Array of Symbols telling AuthLane which attributes of the User model that is used to identify Application useres should be serialized into `SerializedUser`. It is recommended to not store the whole User object, but note that the *id* (or however the unique identifier of the object is named) attribute is required.
@@ -0,0 +1,26 @@
1
+ require 'authlane/version'
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ namespace :doc do
7
+ desc 'Generate YARD documentation database'
8
+ task :generate do
9
+ system 'rm -rf .yardoc'
10
+ system 'yardoc'
11
+ end
12
+
13
+ desc 'Start the YARD documentation server'
14
+ task :srv do
15
+ system 'yard server --reload -- Kargo'
16
+ end
17
+
18
+ desc 'Open generated YARD documentation website'
19
+ task :open do
20
+ system 'open doc/index.html'
21
+ end
22
+ end
23
+
24
+ RSpec::Core::RakeTask.new(:spec)
25
+
26
+ Dir['tasks/*.rake'].sort.each { |ext| load ext }
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'authlane/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'authlane'
8
+ spec.version = Authlane::VERSION
9
+ spec.authors = ['Patrick Lam']
10
+ spec.email = ['zidizei@gmail.com']
11
+ spec.summary = 'Easy User authentication and roles for Sinatra.'
12
+ spec.description = 'The AuthLane Sinatra Extension allows easy User authentication with support for different User roles.'
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'sinatra'
22
+ spec.add_dependency 'sinatra-contrib'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.5'
25
+ spec.add_development_dependency 'rake', '~> 10.1'
26
+ spec.add_development_dependency 'rspec', '~> 2.6'
27
+ spec.add_development_dependency 'yard'
28
+ end
@@ -0,0 +1,191 @@
1
+
2
+ module Sinatra
3
+ module AuthLane
4
+ ##
5
+ # The **AuthLane** `Helpers` are helper methods that are made available to an application's
6
+ # route definitions. It is the main interface between a Sinatra Application and AuthLane
7
+ # and enables easy interaction with the authorization logic by calling a specific helper method.
8
+ #
9
+ module Helpers
10
+ ##
11
+ # @note This method uses {#authorized?} to decide, whether to redirect users to `failed_route`.
12
+ # Check if a user is authorized to view a route.
13
+ #
14
+ # It utilizes the *Role* and *Remember Strategy* to see if a user can access the route this
15
+ # helper method is called from. The first query inside this method is to look for logged in
16
+ # user credentials in the Session. If this fails, *AuthLane* attempts to login the user
17
+ # via Cookie token by calling the *Remember Strategy* that is defined for the application. If it succeeds,
18
+ # this method will continue normally (as in the user was already logged in 'regularly') and
19
+ # use the *Role Strategy* to check user privileges for the route, in case there were any specified when
20
+ # it was being called.
21
+ #
22
+ # @example
23
+ # get '/account' do
24
+ # protect!
25
+ #
26
+ # mustache :account
27
+ # end
28
+ #
29
+ # get '/admin' do
30
+ # protect! roles: [:Admin],
31
+ # failed_route: '/account'
32
+ #
33
+ # mustache :admin
34
+ # end
35
+ #
36
+ # @param [Array] roles A list of role/privilege requirements for a route, set to `nil` to ignore user roles
37
+ # @param [String] failed_route Custom route to redirect to in case the user is not authorized
38
+ #
39
+ # @see Sinatra::AuthLane.create_role_strategy create_role_strategy
40
+ # @see Sinatra::AuthLane.create_remember_strategy create_remember_strategy
41
+ #
42
+ def protect!(roles: nil, failed_route: nil)
43
+ # check for custom :failed_route option
44
+ failed_route ||= settings.authlane[:failed_route]
45
+
46
+ redirect failed_route unless authorized?(roles: roles)
47
+ end
48
+
49
+ alias_method :protected, :protect!
50
+
51
+ ##
52
+ # @note For more information about how this method works, refer to {#protect!}.
53
+ #
54
+ # Returns a `Boolean` depending on the user's authorization status. This can be useful if
55
+ # a route wants to handle unauthorized users differently than just redirecting them to the
56
+ # `:failed_route` setting.
57
+ #
58
+ # @example
59
+ # get '/' do
60
+ # if authorized?
61
+ # mustache :welcome
62
+ # else
63
+ # mustache :whoareyou
64
+ # end
65
+ # end
66
+ #
67
+ # @param [Array] roles A list of role/privilege requirements for a route, set to `nil` to ignore user roles
68
+ #
69
+ # @return [Boolean] `true` if the user is authorized to view a route, `false` otherwise.
70
+ #
71
+ # @see Sinatra::AuthLane::Helpers#protect! protect!
72
+ #
73
+ def authorized?(roles: nil)
74
+ # So, if session[settings.authlane[:session_key]] is available
75
+ # we're home, otherwise, see if the 'Remember Me' strategy
76
+ # can come up with some User credentials.
77
+ if session[settings.authlane[:session_key]].nil?
78
+ remember_strat = self.instance_eval(&settings.authlane[:remember_strategy])
79
+
80
+ if remember_strat
81
+ user = Sinatra::AuthLane::SerializedUser.new(remember_strat, settings.authlane[:serialize_user])
82
+
83
+ # The strategy doesn't log in a User,
84
+ # it just comes up with the credentials to do that.
85
+ # The login actually happens right here.
86
+ session[settings.authlane[:session_key]] = user
87
+ end
88
+ else
89
+ user = session[settings.authlane[:session_key]]
90
+ end
91
+
92
+ if user.nil? || !user
93
+ # Ok, looks like the User needs to gtfo.
94
+ return false
95
+ else
96
+ # User is logged in ...
97
+ unless roles.nil?
98
+ # ... but hold up, he might not have the necessary
99
+ # privileges to access this particular route.
100
+ # Let's ask the 'Role' strategy.
101
+ strat = self.instance_exec roles, &settings.authlane[:role_strategy]
102
+ return false unless strat
103
+ end
104
+ end
105
+
106
+ return true
107
+ end
108
+
109
+ ##
110
+ # Marks a route as the login route, that typically receives `POST` data from a `<form>`.
111
+ #
112
+ # The actual handling of the login logic and anything else the application
113
+ # requires is defined by creating the *Auth Strategy* using {Sinatra::AuthLane.create_auth_strategy create_auth_strategy}.
114
+ # the code block will be called within `authorize!` and - depending on the outcome (refer to
115
+ # {Sinatra::AuthLane.create_auth_strategy create_auth_strategy}'s documentation of its return value) - redirects
116
+ # to the `:failed_route` AuthLane setting or lets the user 'through' to access the route, which - in most cases - should
117
+ # be a redirect to the protected route.
118
+ #
119
+ # @example
120
+ # get '/signin' do
121
+ # mustache :signin
122
+ # end
123
+ #
124
+ # post '/signin' do
125
+ # authorize!
126
+ #
127
+ # redirect '/account'
128
+ # end
129
+ #
130
+ # @return [void]
131
+ #
132
+ # @see Sinatra::AuthLane.create_auth_strategy create_auth_strategy
133
+ #
134
+ def authorize!
135
+ strat = self.instance_exec &settings.authlane[:auth_strategy]
136
+
137
+ unless strat
138
+ redirect settings.authlane[:failed_route], 303
139
+ end
140
+
141
+ session[settings.authlane[:session_key]] = Sinatra::AuthLane::SerializedUser.new(strat, settings.authlane[:serialize_user])
142
+ end
143
+
144
+ ##
145
+ # Marks a route as the logout route.
146
+ #
147
+ # It resets the current Session and deletes any Session data along with it, particularly
148
+ # the *AuthLane* Session data. The *Forget Strategy* will be called here as well to
149
+ # 'counteract' the *Remember Me* functionality that is implemented in the *Remember* and
150
+ # *Auth Strategy*.
151
+ #
152
+ # @example
153
+ # get '/signout' do
154
+ # unauthorize!
155
+ # end
156
+ #
157
+ # @return [void]
158
+ #
159
+ # @see Sinatra::AuthLane.create_forget_strategy create_forget_strategy
160
+ #
161
+ def unauthorize!
162
+ token = cookies[settings.authlane[:remember_cookie]]
163
+
164
+ self.instance_exec(token, &settings.authlane[:forget_strategy]) unless token.nil?
165
+
166
+ # @private
167
+ cookies.delete(settings.authlane[:remember_cookie])
168
+ session.destroy
169
+ end
170
+
171
+ ##
172
+ # Gets the credentials of the currently logged in user.
173
+ #
174
+ # @example
175
+ # get '/account' do
176
+ # authorized?
177
+ #
178
+ # @username = current_user.username
179
+ # @email = current_user[:email] # This works too (refer to SerializedUser for more info)
180
+ #
181
+ # mustache :account
182
+ # end
183
+ #
184
+ # @return [SerializedUser] the user data serialized into the Session.
185
+ #
186
+ def current_user
187
+ session[settings.authlane[:session_key]]
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,85 @@
1
+
2
+ module Sinatra
3
+ module AuthLane
4
+ ##
5
+ # Storage class for logged in user credentials.
6
+ # Behaves like a Hash **and** an Object.
7
+ #
8
+ class SerializedUser
9
+ ##
10
+ # Sets up the Object to be serialized.
11
+ #
12
+ # Receives an Object `user` and stores its
13
+ # attributes specified by `attributes` in a Hash.
14
+ # If `attributes` is empty, the whole object
15
+ # will be stored as-is.
16
+ #
17
+ # @param [Object] user The User object that needs to be serialized
18
+ # @param [Array<Symbol>] attributes A list of attribute names to be serialized from `user`
19
+ #
20
+ def initialize(user, attributes = [])
21
+ if attributes.size == 0
22
+ @user = user
23
+ else
24
+ @user = Hash.new
25
+
26
+ attributes.each do |attrs|
27
+ @user[attrs] = user.__send__(attrs.to_sym) if user.respond_to? attrs
28
+ end
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Access stored attributes like a Hash.
34
+ #
35
+ # If the whole Object was stored, it sends the
36
+ # Hash key `a` as a message to that Object.
37
+ #
38
+ # @param [String, Symbol] a The name of the serialized object's attribute to be read
39
+ #
40
+ def [](a)
41
+ (@user.is_a? Hash) ? @user[a] : @user.__send__(a.to_sym)
42
+ end
43
+
44
+ ##
45
+ # Enables Object-like access to the
46
+ # stored attributes.
47
+ #
48
+ # If the whole Object was stored, it sends the
49
+ # method name `m` as a message to that Object.
50
+ # Otherwise it will access the Hash using `m` as
51
+ # the key.
52
+ #
53
+ # @param [Symbol] m The name of the serialized object's attribute to be read
54
+ #
55
+ def method_missing(m, *args, &block)
56
+ if @user.is_a? Hash
57
+ @user[m]
58
+ else
59
+ @user.__send__(m.to_sym, args)
60
+ end
61
+ end
62
+
63
+ ##
64
+ # Return a Hash representing the serialized object's attributes and values.
65
+ #
66
+ # If the whole Object was stored its `to_h` will be called.
67
+ # In either case, attribute names can be accessed by Symbol or String
68
+ # key alike.
69
+ #
70
+ # @return [Hash] the Hash representation of the stored object.
71
+ #
72
+ def to_h
73
+ universal_hash = Hash.new
74
+ hash = @user.to_h
75
+ hash.each_pair do |key, value|
76
+ universal_hash[key.to_s] = value if key.is_a? Symbol
77
+
78
+ universal_hash[key] = value
79
+ end
80
+
81
+ universal_hash
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module Authlane
3
+ ##
4
+ # Current AuthLane version number.
5
+ VERSION = '1.0.0'
6
+ end
@@ -0,0 +1,229 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/cookies'
3
+
4
+ require 'authlane/version'
5
+ require 'authlane/serializeduser'
6
+ require 'authlane/helper'
7
+
8
+ module Sinatra
9
+ ##
10
+ # The `AuthLane` Sinatra Extension allows easy User
11
+ # authentication with support for different User *roles*
12
+ # and automatic login via Cookies. It exposes {Sinatra::AuthLane::Helpers Helper}
13
+ # methods to tell which routes are protected or involved in the authentication process.
14
+ #
15
+ # The actual authentication logic (*strategy*) is defined by the Application using
16
+ # a namespaced DSL provided by this extension, while the general Extension configuration
17
+ # is handled with Sinatra's `set` method, which will be described in more detail below.
18
+ #
19
+ # # Configuring AuthLane
20
+ #
21
+ # **AuthLane**'s configuration data is available under Sinatra's `settings` object
22
+ # with the key `:authlane` as a Hash, so changing config values is simply done with
23
+ # Sinatra's `set` method.
24
+ #
25
+ # ```
26
+ # set :authlane, :failed_route => '/login'
27
+ # ```
28
+ #
29
+ # The following settings can be customize (the used values are their defaults):
30
+ #
31
+ # ```
32
+ # set :authlane, :failed_route => '/user/unauthorized',
33
+ # :session_key => :authlane,
34
+ # :remember_cookie => :authlane_token,
35
+ # :serialize_user => [:id]
36
+ # ```
37
+ #
38
+ # ## `:failed_route`
39
+ #
40
+ # The `:failed_route` sets the route String, where AuthLane should redirect to in case a
41
+ # route requires authorisation and the User ist not logged in. It typically is the route
42
+ # to display the login form, but can be set to anything that is needed, as long the it is
43
+ # not protected by authorisation as well.
44
+ #
45
+ # ## `:seession_key`
46
+ #
47
+ # The `:session_key` sets the name (as a Symbol) of the Session variable where User credentials of a logged in
48
+ # User are stored. The stored User data are wrapped inside a {Sinatra::AuthLane::SerializedUser SerializedUser}
49
+ # object and can be retrieved by using Sinatra's `session` helper and giving it the key that is defined here
50
+ # `session[:authlane]`. Alternatively, the AuthLane {Sinatra::AuthLane::Helpers Helper} exposes the method
51
+ # {Sinatra::AuthLane::Helpers#current_user current_user} to provide easy access to User data.
52
+ #
53
+ # ## `:remember_cookie`
54
+ #
55
+ # Customize the Cookie's name that stores the token hash used for the *Remember Me* functionality.
56
+ # The setting (and creation) of the token needs to be implemented by the Extension user in
57
+ # both the Auth and Remember Strategy.
58
+ #
59
+ # ## `:serialize_user`
60
+ #
61
+ # The `:serialized_user` settings contains an Array of Symbols telling AuthLane which attributes
62
+ # of the User model that is used to identify Application useres should
63
+ # be serialized into {Sinatra::AuthLane::SerializedUser SerializedUser}. It is recommended to not
64
+ # store the whole User object, but note that the *id* (or however the unique identifier
65
+ # of the object is named) attribute is required.
66
+ #
67
+ module AuthLane
68
+ ##
69
+ # Initiates **AuthLane** when it is being registered with a Sinatra Application.
70
+ #
71
+ # Adds helper methods to App instance and sets the default
72
+ # settings described above.
73
+ #
74
+ # @todo Don't pass `app` to instance variable `@app`. Implement proper encapsulation of AuthLane.
75
+ #
76
+ # @api Sinatra
77
+ #
78
+ def self.registered(app)
79
+ app.helpers AuthLane::Helpers
80
+
81
+ @app = app
82
+
83
+ # default configuration
84
+ app.set :authlane,
85
+ :failed_route => '/user/unauthorized', # route to redirect to if the user is required to login
86
+ :session_key => :authlane, # name of the Session key to store the login data
87
+ :remember_cookie => :authlane_token, # Cookie name to store 'Remember Me' token
88
+ :auth_strategy => Proc.new { false }, # strategy to be executed to log in users
89
+ :role_strategy => Proc.new { true }, # strategy to be executed to check permissions and roles
90
+ :remember_strategy => Proc.new { false }, # strategy to be executed to log in users via 'Remember Me' token
91
+ :forget_strategy => Proc.new { false }, # strategy to be executed when logging out and 'forgetting' the user
92
+ :serialize_user => [:id] # specify User model fields to be serialized into the login session
93
+ end
94
+
95
+ class << self
96
+ ##
97
+ # Create the **Auth** *Strategy* for AuthLane.
98
+ #
99
+ # Used from the Sinatra DSL context to define the **Auth** *Strategy*
100
+ # to be used by passing the implementation as a Code block. It is then
101
+ # stored as a `Proc` object and will be called by AuthLane when needed.
102
+ #
103
+ # The following describes the `Proc` objects API requirements. It is
104
+ # required by the implemented strategy to follow these to be usable by AuthLane.
105
+ #
106
+ # @note While the **Auth** Strategy is primarily responsible for logging in users,
107
+ # it usually needs to implement some *Remember Me* logic as well.
108
+ #
109
+ # @return [False, Object] The strategy needs to return the User Model object of
110
+ # the user that successfully logged in or - in case of failure - `false`.
111
+ #
112
+ # @example
113
+ # Sinatra::AuthLane.create_auth_strategy do
114
+ # user = User.find_by_username(params[:username])
115
+ #
116
+ # (!user.nil? && user.password == params[:pass]) ? user : false
117
+ # end
118
+ #
119
+ # @see Sinatra::AuthLane::Helpers#authorize! authorize!
120
+ #
121
+ # @api AuthLane
122
+ #
123
+ def create_auth_strategy
124
+ @app.set :authlane, :auth_strategy => Proc.new
125
+ end
126
+
127
+ ##
128
+ # Create the **Role** *Strategy* for AuthLane.
129
+ #
130
+ # Used from the Sinatra DSL context to define the **Role** *Strategy*
131
+ # to be used by passing the implementation as a Code block. It is then
132
+ # stored as a `Proc` object and will be called by AuthLane when needed.
133
+ #
134
+ # The following describes the `Proc` objects API requirements. It is
135
+ # required by the implemented strategy to follow these to be usable by AuthLane.
136
+ #
137
+ # @example
138
+ # Sinatra::AuthLane.create_role_strategy do |roles|
139
+ # user = current_user # AuthLane helper to get the currently logged in user data
140
+ #
141
+ # roles.include? user.role # See if the list of role names in `roles` contains the user's role
142
+ # end
143
+ #
144
+ # @param [Array] roles The `Proc` receives a list of role names (e.g. as Symbols) a User needs to fullfill
145
+ # to be allowed to see the route. The list is passed by the {Sinatra::AuthLane::Helpers#authorized? authorized?}
146
+ # helper.
147
+ #
148
+ # @return [Boolean] The strategy returns `true`, if the user met the necessary role requirements or `false` otherwise.
149
+ #
150
+ # @see Sinatra::AuthLane::Helpers#authorized? authorized?
151
+ #
152
+ # @api AuthLane
153
+ #
154
+ def create_role_strategy
155
+ @app.set :authlane, :role_strategy => Proc.new
156
+ end
157
+
158
+ ##
159
+ # Create the **Remember** *Strategy* for AuthLane.
160
+ #
161
+ # Used from the Sinatra DSL context to define the **Remember** *Strategy*
162
+ # to be used by passing the implementation as a Code block. It is then
163
+ # stored as a `Proc` object and will be called by AuthLane when needed.
164
+ #
165
+ # The following describes the `Proc` objects API requirements. It is
166
+ # required by the implemented strategy to follow these to be usable by AuthLane.
167
+ #
168
+ # @note The **Remember** Strategy is only responsible for automatically logging in a user.
169
+ # The necessary Cookie token (or whatever technique) is usually set in the **Auth** Strategy.
170
+ #
171
+ # @example
172
+ # Sinatra::AuthLane.create_remember_strategy do
173
+ # remember_token = cookies[:authlane_token]
174
+ # remembered_user = User.find_by_token(remember_token)
175
+ #
176
+ # (remembered_user.nil?) ? false : remembered_user
177
+ # end
178
+ #
179
+ # @return [False, Object] The strategy needs to return the User Model object of
180
+ # the user that successfully logged in or - in case of failure - `false`.
181
+ #
182
+ # @see Sinatra::AuthLane::Helpers#authorized? authorized?
183
+ #
184
+ # @api AuthLane
185
+ #
186
+ def create_remember_strategy
187
+ @app.set :authlane, :remember_strategy => Proc.new
188
+ end
189
+
190
+ ##
191
+ # Create the **Forget** *Strategy* for AuthLane.
192
+ #
193
+ # Used from the Sinatra DSL context to define the **Forget** *Strategy*
194
+ # to be used by passing the implementation as a Code block. It is then
195
+ # stored as a `Proc` object and will be called by AuthLane when needed.
196
+ #
197
+ # The following describes the `Proc` objects API requirements. It is
198
+ # required by the implemented strategy to follow these to be usable by AuthLane.
199
+ #
200
+ # @note The **Forget** Strategy is the counter-part to the **Remember** Strategy.
201
+ # It's responsible for disabling the auto login technique and is called when logging out.
202
+ #
203
+ # @note While the *Auth* and *Remember Strategy* needs to interact with the Cookie token directly,
204
+ # the *Forget Strategy* does not need to implement the deletion of the Cookie.
205
+ # This is done automatically by AuthLane behind the scenes.
206
+ #
207
+ # @example
208
+ # Sinatra::AuthLane.create_forget_strategy do |token|
209
+ # user = User.find(current_user.id)
210
+ # user.token = nil if user.token == token
211
+ # end
212
+ #
213
+ # @param [Object] token The `Proc` object receives the *Remember Me* token of the current user.
214
+ #
215
+ # @see Sinatra::AuthLane::Helpers#unauthorize! unauthorize!
216
+ #
217
+ # @return [void]
218
+ #
219
+ # @api AuthLane
220
+ #
221
+ def create_forget_strategy
222
+ @app.set :authlane, :forget_strategy => Proc.new
223
+ end
224
+ end
225
+ end
226
+
227
+ helpers Sinatra::Cookies
228
+ register Sinatra::AuthLane
229
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path '../helper.rb', __FILE__
2
+
3
+ describe Sinatra::AuthLane::Helpers do
4
+ before :all do
5
+ mock_app do
6
+ helpers Sinatra::Cookies
7
+ register Sinatra::AuthLane
8
+
9
+ use Rack::Session::Cookie, :secret => 'rspec'
10
+
11
+ Sinatra::AuthLane.create_auth_strategy { { id: 1 } }
12
+
13
+ get '/protected' do
14
+ protect!
15
+ end
16
+
17
+ get '/authorized' do
18
+ if authorized?
19
+ 'authorized'
20
+ else
21
+ 'unauthorized'
22
+ end
23
+ end
24
+
25
+ get '/authorize' do
26
+ authorize!
27
+ protect!
28
+ end
29
+
30
+ get '/unauthorize' do
31
+ unauthorize!
32
+ protect!
33
+ end
34
+
35
+ get '/user' do
36
+ protect!
37
+ user = current_user
38
+ user
39
+ end
40
+ end
41
+ end
42
+
43
+ it "should be able to recognize authorized states" do
44
+ get '/authorized', {}, { 'rack.session' => { authlane: { id: 1 } } }
45
+ expect(last_response.body).to eq('authorized')
46
+ end
47
+
48
+ it "should be able to recognize unauthorized states" do
49
+ get '/authorized'
50
+ expect(last_response.body).to eq('unauthorized')
51
+ end
52
+
53
+ it "should redirect from protected route to signin page when not logged in" do
54
+ get '/protected'
55
+ expect(last_response.headers['location']).to eq('http://example.org/user/unauthorized')
56
+ end
57
+
58
+ it "should display protected route when logged in" do
59
+ get '/protected', {}, { 'rack.session' => { authlane: '1' } }
60
+ last_response.should be_ok
61
+ end
62
+
63
+ it "should authorize a User" do
64
+ get '/authorize'
65
+ last_response.should be_ok
66
+ end
67
+
68
+ it "should unauthorize a User" do
69
+ get '/unauthorize', {}, { 'rack.session' => { authlane: '1' } }
70
+ expect(last_response.headers['location']).to eq('http://example.org/user/unauthorized')
71
+ end
72
+
73
+ it "should be able to get the current User's serialized credentials" do
74
+ get '/user', {}, { 'rack.session' => { authlane: '1' } }
75
+ expect(last_response.body).to eq('1')
76
+ end
77
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path '../helper.rb', __FILE__
2
+
3
+ describe Sinatra::AuthLane::SerializedUser do
4
+ def mock_user
5
+ Sinatra::AuthLane::SerializedUser.new({ id: 1, name: 'rspec' })
6
+ end
7
+
8
+ it 'can be accessed like a Hash' do
9
+ user = mock_user
10
+ user.should respond_to(:[])
11
+ user[:id].should eql(1)
12
+ user[:name].should eql('rspec')
13
+ end
14
+
15
+ it 'can be accessed like an Object' do
16
+ user = mock_user
17
+ user.should respond_to(:method_missing)
18
+ user.id.should eql(1)
19
+ user.name.should eql('rspec')
20
+ end
21
+
22
+ it 'should return Hash of the serialized User object' do
23
+ user = mock_user
24
+ user.to_h.should be_a(Hash)
25
+ end
26
+
27
+ it 'should return Hash of the serialized User object with String keys' do
28
+ user = mock_user.to_h
29
+ user['id'].should eql(1)
30
+ user['name'].should eql('rspec')
31
+ end
32
+
33
+ it 'should return Hash of the serialized User object with Symbol keys' do
34
+ user = mock_user.to_h
35
+ user[:id].should eql(1)
36
+ user[:name].should eql('rspec')
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'sinatra/test_helpers'
4
+ require 'sinatra/cookies'
5
+
6
+ require 'sinatra/authlane'
7
+
8
+ RSpec.configure do |c|
9
+ c.include Sinatra::TestHelpers
10
+ c.include Rack::Test::Methods
11
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path '../helper.rb', __FILE__
2
+
3
+ describe Sinatra::AuthLane do
4
+ def strategy(type, &block)
5
+ mock_app do
6
+ register Sinatra::AuthLane
7
+
8
+ Sinatra::AuthLane.send "create_#{type}_strategy", &block
9
+ end
10
+ end
11
+
12
+ it "should allow definition of auth strategy" do
13
+ strategy(:auth) do
14
+ 'rspec'
15
+ end
16
+
17
+ settings.authlane[:auth_strategy].yield.should == 'rspec'
18
+ end
19
+
20
+ it "should allow definition of role strategy" do
21
+ strategy(:role) do
22
+ 'rspec'
23
+ end
24
+
25
+ settings.authlane[:role_strategy].yield.should == 'rspec'
26
+ end
27
+
28
+ it "should allow definition of remember strategy" do
29
+ strategy(:remember) do
30
+ 'rspec'
31
+ end
32
+
33
+ settings.authlane[:remember_strategy].yield.should == 'rspec'
34
+ end
35
+
36
+ it "should allow definition of forget strategy" do
37
+ strategy(:forget) do
38
+ 'rspec'
39
+ end
40
+
41
+ settings.authlane[:forget_strategy].yield.should == 'rspec'
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authlane
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Patrick Lam
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra-contrib
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: The AuthLane Sinatra Extension allows easy User authentication with support
98
+ for different User roles.
99
+ email:
100
+ - zidizei@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".yardopts"
107
+ - Gemfile
108
+ - LICENSE.md
109
+ - README.md
110
+ - Rakefile
111
+ - authlane.gemspec
112
+ - lib/authlane/helper.rb
113
+ - lib/authlane/serializeduser.rb
114
+ - lib/authlane/version.rb
115
+ - lib/sinatra/authlane.rb
116
+ - spec/authlane_helper_spec.rb
117
+ - spec/authlane_serializeduser_spec.rb
118
+ - spec/helper.rb
119
+ - spec/sinatra_authlane_spec.rb
120
+ homepage: ''
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.4.1
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Easy User authentication and roles for Sinatra.
144
+ test_files:
145
+ - spec/authlane_helper_spec.rb
146
+ - spec/authlane_serializeduser_spec.rb
147
+ - spec/helper.rb
148
+ - spec/sinatra_authlane_spec.rb
149
+ has_rdoc: