nitro-auth 0.2.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.
- data/CHANGELOG +12 -0
- data/LICENSE +31 -0
- data/README +115 -0
- data/TODO +7 -0
- data/examples/basic/public/error.xhtml +81 -0
- data/examples/basic/public/js/behaviour.js +254 -0
- data/examples/basic/public/js/controls.js +446 -0
- data/examples/basic/public/js/dragdrop.js +537 -0
- data/examples/basic/public/js/effects.js +612 -0
- data/examples/basic/public/js/prototype.js +1038 -0
- data/examples/basic/public/register.xhtml +65 -0
- data/examples/basic/run.rb +32 -0
- data/examples/basic/src/basic.rb +4 -0
- data/examples/basic/src/basic/auth_controller.rb +9 -0
- data/examples/basic/src/basic/controller.rb +29 -0
- data/examples/basic/src/basic/site.rb +13 -0
- data/examples/basic/src/basic/user.rb +22 -0
- data/examples/basic/src/basic/view/index.xhtml +35 -0
- data/lib/nitro/auth.rb +41 -0
- data/lib/nitro/auth/auth_controller.rb +199 -0
- data/lib/nitro/auth/controller.rb +166 -0
- data/lib/nitro/auth/model/user.rb +147 -0
- data/lib/nitro/auth/util/crypt.rb +55 -0
- data/lib/nitro/auth/view/access_denied.xhtml +13 -0
- data/lib/nitro/auth/view/login.xhtml +38 -0
- data/lib/nitro/auth/view/logout.xhtml +16 -0
- data/lib/nitro/auth/view/register.xhtml +51 -0
- metadata +90 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
6
|
+
<head>
|
7
|
+
<title>Register a new account.</title>
|
8
|
+
<meta http-equiv="Content-Script-Type" content="text/javascript"/>
|
9
|
+
#{include_script "/js/behaviour.js"}
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<h1>Register a new account.</h1>
|
13
|
+
<form id="new_user_form" action="#{new_user_url}" method="POST">
|
14
|
+
<table>
|
15
|
+
<tr>
|
16
|
+
<td><label for="login">Login:</label></td>
|
17
|
+
<td>
|
18
|
+
<input id="login" name="login" type="text"
|
19
|
+
value="#{@new_user.login}"/>
|
20
|
+
</td>
|
21
|
+
</tr>
|
22
|
+
<tr>
|
23
|
+
<td><label for="name">Name:</label></td>
|
24
|
+
<td>
|
25
|
+
<input id="name" name="name" type="text"
|
26
|
+
value="#{@new_user.name}"/>
|
27
|
+
</td>
|
28
|
+
</tr>
|
29
|
+
<tr>
|
30
|
+
<td><label for="email">Email Address:</label></td>
|
31
|
+
<td>
|
32
|
+
<input id="email" name="email" type="text"
|
33
|
+
value="#{@new_user.email}"/>
|
34
|
+
</td>
|
35
|
+
</tr>
|
36
|
+
<tr>
|
37
|
+
<td><label for="password">Password:</label></td>
|
38
|
+
<td><input id="password" name="password" type="password"/></td>
|
39
|
+
</tr>
|
40
|
+
<tr>
|
41
|
+
<td><label for="confirm_password">Confirm Password:</label></td>
|
42
|
+
<td>
|
43
|
+
<input id="confirm_password" name="confirm_password"
|
44
|
+
type="password"/>
|
45
|
+
</td>
|
46
|
+
</tr>
|
47
|
+
<tr>
|
48
|
+
<td colspan="2">
|
49
|
+
<button id="register_new_user" type="button">Register!</button>
|
50
|
+
</td>
|
51
|
+
</tr>
|
52
|
+
<tr>
|
53
|
+
<td id="error" colspan="2">#{@error}</td>
|
54
|
+
</tr>
|
55
|
+
</table>
|
56
|
+
</form>
|
57
|
+
<p class="login_link">
|
58
|
+
<a href="#{login_url}">Go back to the login page</a>.
|
59
|
+
</p>
|
60
|
+
<p class="back_link">
|
61
|
+
<a href="#{@backlink}">Go back to where I started</a>.
|
62
|
+
</p>
|
63
|
+
#{helper_script}
|
64
|
+
</body>
|
65
|
+
</html>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'nitro'
|
3
|
+
|
4
|
+
require 'basic'
|
5
|
+
|
6
|
+
include Nitro
|
7
|
+
|
8
|
+
# Because it's not synced by default on Windows
|
9
|
+
STDERR.sync = true if not STDERR.sync
|
10
|
+
|
11
|
+
# Assuming you set up a database with these values...
|
12
|
+
db_config = {
|
13
|
+
:store => 'mysql',
|
14
|
+
:name => 'basic',
|
15
|
+
:user => 'basic',
|
16
|
+
:password => 'basic'
|
17
|
+
}
|
18
|
+
Og.setup(db_config)
|
19
|
+
|
20
|
+
# Tell the AuthController which User class to create
|
21
|
+
# on new-user registration.
|
22
|
+
Auth::AuthController.user_class = Basic::User
|
23
|
+
|
24
|
+
# Set up the dispatcher, including an AuthController
|
25
|
+
# at /auth.
|
26
|
+
Server.map = {
|
27
|
+
'/' => Basic::BasicController,
|
28
|
+
'/auth' => Basic::AuthController
|
29
|
+
}
|
30
|
+
|
31
|
+
# Run the example.
|
32
|
+
Nitro.run()
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'nitro'
|
2
|
+
require 'nitro/auth'
|
3
|
+
|
4
|
+
module Basic
|
5
|
+
class BasicController < Nitro::Controller
|
6
|
+
include Auth::Controller
|
7
|
+
|
8
|
+
@template_root = File.dirname(__FILE__) + "/view"
|
9
|
+
|
10
|
+
scaffold Site, :index => true
|
11
|
+
|
12
|
+
def list_site
|
13
|
+
if user.has_role? Auth.admin_role
|
14
|
+
@sites = Site.all()
|
15
|
+
else
|
16
|
+
@sites = Site.find(:where => "owner_oid = #{user.oid}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
protect :index
|
20
|
+
protect :list_site
|
21
|
+
protect :edit_site
|
22
|
+
protect :view_site
|
23
|
+
protect :save_site
|
24
|
+
protect :del_site
|
25
|
+
protect :delete_site
|
26
|
+
|
27
|
+
administrative :new_site
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'og'
|
2
|
+
|
3
|
+
module Basic
|
4
|
+
# A sample application object, just to illustrate.
|
5
|
+
class Site
|
6
|
+
property :url, String, :sql => "varchar(255) not null unique",
|
7
|
+
:unique => true
|
8
|
+
property :title, String, :sql => "varchar(255) not null unique",
|
9
|
+
:unique => true
|
10
|
+
|
11
|
+
belongs_to :owner, User
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'nitro/auth'
|
2
|
+
|
3
|
+
module Basic
|
4
|
+
# An extended user object.
|
5
|
+
class User < Auth::User
|
6
|
+
# Add name and email fields.
|
7
|
+
property :name, String, :sql => "varchar(255) not null"
|
8
|
+
property :email, String, :sql => "varchar(255) not null unique",
|
9
|
+
:unique => true
|
10
|
+
|
11
|
+
# Add a relationship with an application object.
|
12
|
+
has_many Site
|
13
|
+
|
14
|
+
def initialize(login = nil, password = nil, parameters = {})
|
15
|
+
super(login, password, parameters)
|
16
|
+
# Should probably sanity check these, since they're coming
|
17
|
+
# in over the web, but this'll do for now.
|
18
|
+
@name = parameters["name"]
|
19
|
+
@email = parameters["email"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
6
|
+
<head>
|
7
|
+
<title>Basic Nitro-Auth Example</title>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<h1>Basic Nitro-Auth Example</h1>
|
11
|
+
<table>
|
12
|
+
<tr>
|
13
|
+
<td class="sidebar">
|
14
|
+
<a href="/">List sites</a>
|
15
|
+
<?r if user.has_role? Auth.admin_role ?>
|
16
|
+
<br/><a href="/new_site">Add a new site</a>
|
17
|
+
<?r end ?>
|
18
|
+
</td>
|
19
|
+
<td>
|
20
|
+
<table>
|
21
|
+
<?r for site in @sites ?>
|
22
|
+
<tr>
|
23
|
+
<td><a href="#{site.url}">#{site.title}</a></td>
|
24
|
+
<td>Located at #{site.url}</td>
|
25
|
+
<td if="user.has_role? Auth.admin_role">
|
26
|
+
Owned by #{site.owner.name}
|
27
|
+
</td>
|
28
|
+
</tr>
|
29
|
+
<?r end ?>
|
30
|
+
</table>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
</table>
|
34
|
+
</body>
|
35
|
+
</html>
|
data/lib/nitro/auth.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'glue'
|
2
|
+
|
3
|
+
# Auth provides authentication and authorization for Nitro.
|
4
|
+
module Auth
|
5
|
+
Version = '0.2.0'
|
6
|
+
|
7
|
+
#--
|
8
|
+
# Things I wish were settings, but setting doesn't appear to do
|
9
|
+
# what I'm needing...
|
10
|
+
# FIXME: Figure out some sane way to put these in config.
|
11
|
+
#
|
12
|
+
# setting :admin_role, :default => 'admin',
|
13
|
+
# :doc => 'The administrative/superuser role name.'
|
14
|
+
# setting :user_role, :default => 'user',
|
15
|
+
# :doc => 'The default role name (all users get this by default).'
|
16
|
+
# setting :session_key_expiration, :default => 60 * 60 * 24 * 30,
|
17
|
+
# :doc => 'Number of seconds session keys last before expiring.'
|
18
|
+
#++
|
19
|
+
|
20
|
+
# The administrative/superuser role name.
|
21
|
+
def self.admin_role
|
22
|
+
'admin'
|
23
|
+
end
|
24
|
+
|
25
|
+
# The default role name (all users get this by default).
|
26
|
+
def self.user_role
|
27
|
+
'user'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Number of seconds session keys last before expiring.
|
31
|
+
def self.session_key_expiration
|
32
|
+
60*60*24*30
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'nitro/auth/util/crypt'
|
37
|
+
|
38
|
+
require 'nitro/auth/model/user'
|
39
|
+
|
40
|
+
require 'nitro/auth/controller'
|
41
|
+
require 'nitro/auth/auth_controller'
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require 'glue/uri'
|
4
|
+
|
5
|
+
require 'nitro'
|
6
|
+
require 'nitro/mixin/javascript'
|
7
|
+
|
8
|
+
module Auth
|
9
|
+
# Provides basic login actions.
|
10
|
+
#
|
11
|
+
# In theory, +AuthController+ can be easily integrated into your
|
12
|
+
# application. Or, at least, that's the goal -- we're early enough
|
13
|
+
# that it hasn't been tested much yet.
|
14
|
+
class AuthController < Nitro::Controller
|
15
|
+
include Nitro::JavascriptMixin
|
16
|
+
|
17
|
+
# Override the template_root for this controller.
|
18
|
+
@template_root = File.dirname(__FILE__) + "/view"
|
19
|
+
|
20
|
+
# Spits out the url to the register-a-new-user page.
|
21
|
+
def register_url
|
22
|
+
"#{controller_name}/register"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Spits out the url to the login page.
|
26
|
+
def login_url
|
27
|
+
"#{controller_name}/login"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Spits out the url to the try-to-login action.
|
31
|
+
def try_login_url
|
32
|
+
"#{controller_name}/try_login"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Spits out the url to the create-a-new-user action.
|
36
|
+
def new_user_url
|
37
|
+
"#{controller_name}/new_user"
|
38
|
+
end
|
39
|
+
|
40
|
+
# The action run when the current user doesn't have sufficient
|
41
|
+
# permissions to run the requested action.
|
42
|
+
def access_denied
|
43
|
+
@backlink = session["prelogin_referer"]
|
44
|
+
session.delete "prelogin_uri"
|
45
|
+
session.delete "prelogin_referer"
|
46
|
+
end
|
47
|
+
|
48
|
+
# The main login action.
|
49
|
+
def login
|
50
|
+
@login_name = request.params["login"].to_s
|
51
|
+
@login_name = nil if @login_name.empty?
|
52
|
+
@error = "Login or password incorrect." if "false" == request.params["allowed"]
|
53
|
+
@error = request.params["error"] if request.params["error"]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Log the current user out.
|
57
|
+
def logout
|
58
|
+
Logger.debug "Logging out."
|
59
|
+
session_cookie = Cookie.new "login_session_key", ""
|
60
|
+
response.add_cookie session_cookie
|
61
|
+
end
|
62
|
+
|
63
|
+
# Attempt to log the user in. This is the action at which
|
64
|
+
# login forms should point (preferably using try_login_url).
|
65
|
+
#
|
66
|
+
# Redirects back wherever they originally came from if login
|
67
|
+
# is successful, or the login page again if not.
|
68
|
+
def try_login
|
69
|
+
Logger.debug("Trying to log in.")
|
70
|
+
user = nil
|
71
|
+
login = request.params['login']
|
72
|
+
password = request.params['password']
|
73
|
+
user = User.find_one(:where => "login = '#{login}'") if login
|
74
|
+
Logger.debug("Found user #{user.oid} with login #{login}.") if user
|
75
|
+
|
76
|
+
if user and authenticate(user, password)
|
77
|
+
session_key = user.session_key
|
78
|
+
if session_key
|
79
|
+
session_cookie = Cookie.new "login_session_key", session_key
|
80
|
+
session_cookie.expires = user.session_key_expires
|
81
|
+
response.add_cookie session_cookie
|
82
|
+
end
|
83
|
+
Logger.debug("Sending them back from whence they came.")
|
84
|
+
return_to_original_location
|
85
|
+
end
|
86
|
+
|
87
|
+
Logger.debug("Login incorrect, sending them back to login page.")
|
88
|
+
redirect login_url + "?allowed=false"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the user back to their original location.
|
92
|
+
def return_to_original_location
|
93
|
+
prelogin_uri = session["prelogin_uri"]
|
94
|
+
session.delete "prelogin_uri"
|
95
|
+
session.delete "prelogin_referer"
|
96
|
+
Logger.debug("Redirecting back to #{prelogin_uri}.")
|
97
|
+
redirect prelogin_uri if prelogin_uri
|
98
|
+
|
99
|
+
Logger.debug("No prelogin URI, redirecting back to / instead.")
|
100
|
+
redirect "/"
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# Register a new user.
|
105
|
+
#
|
106
|
+
# Expects a few things of its template:
|
107
|
+
# * Form action should point to new_user (using new_user_url).
|
108
|
+
# * Needs, at least:
|
109
|
+
# * Text input named login.
|
110
|
+
# * Password input named password.
|
111
|
+
# * Password input named confirm_password.
|
112
|
+
# * error (a text element for displaying errors,
|
113
|
+
# should generally be rendered with #{@error} or the like).
|
114
|
+
# * Form submit button named register_new_user.
|
115
|
+
# * Template should use the Nitro javascript mixin appropriately.
|
116
|
+
def register
|
117
|
+
@backlink = session["prelogin_referer"]
|
118
|
+
@error = request.params["error"] || ""
|
119
|
+
@new_user = @@user_class.new(nil, nil, request.params)
|
120
|
+
behaviour "#register_new_user", %{
|
121
|
+
el.onclick = function() {
|
122
|
+
var form = this.form;
|
123
|
+
var error = document.getElementById("error");
|
124
|
+
if (0 == form.login.value.length) {
|
125
|
+
error.innerHTML = "You must provide a login."
|
126
|
+
} else if (0 == form.password.value.length) {
|
127
|
+
error.innerHTML = "You must provide a password."
|
128
|
+
} else if (form.password.value != form.confirm_password.value) {
|
129
|
+
error.innerHTML = "Passwords do not match."
|
130
|
+
} else {
|
131
|
+
form.submit();
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
# Try to create a new user. This is the action at which
|
138
|
+
# "register a new user" forms should point.
|
139
|
+
#
|
140
|
+
# TODO: Allow some sort of password quality check? A plugin, perhaps?
|
141
|
+
# Or just assume that the user object's validation is sufficient?
|
142
|
+
#
|
143
|
+
# TODO: Use Nitro form validation once it's all there.
|
144
|
+
#
|
145
|
+
# TODO: Get rid of these hardcoded error messages.
|
146
|
+
def new_user
|
147
|
+
Logger.debug("Setting up new user.")
|
148
|
+
login = request.params['login']
|
149
|
+
password = request.params['password']
|
150
|
+
confirm = request.params['confirm_password']
|
151
|
+
# Doublecheck. The Javascript should have checked this, but
|
152
|
+
# just in case...
|
153
|
+
registration_error "You must provide a login." if login.empty?
|
154
|
+
registration_error "You must provide a password." if password.empty?
|
155
|
+
registration_error "Passwords do not match." if password != confirm
|
156
|
+
# Further validation is delegated to the user object.
|
157
|
+
|
158
|
+
# See if the login is duplicate
|
159
|
+
existing_user = User.find_one(:where => "login = '#{login}'")
|
160
|
+
registration_error "Login #{login} already exists." if existing_user
|
161
|
+
|
162
|
+
# Okay, try to create it.
|
163
|
+
new_user = @@user_class.create(login, password, request.params)
|
164
|
+
user_role = Role.find_one(:where => "name = '#{Auth.user_role}'")
|
165
|
+
new_user.add_role user_role if user_role
|
166
|
+
Logger.debug("Finished new user setup, trying to log in.")
|
167
|
+
try_login
|
168
|
+
end
|
169
|
+
|
170
|
+
#--
|
171
|
+
# FIXME: This is sort of a hack for now. Ugh.
|
172
|
+
#++
|
173
|
+
|
174
|
+
# Sets the default class all authentication controllers will use
|
175
|
+
# when creating new users.
|
176
|
+
#
|
177
|
+
# Uses Auth::User by default, but generally applications will
|
178
|
+
# extend that class rather than use it bare.
|
179
|
+
def self.user_class=(klass)
|
180
|
+
if klass.is_a? Class
|
181
|
+
@@user_class = klass
|
182
|
+
else
|
183
|
+
raise "Invalid default user_class set in Auth::AuthController."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
@@user_class = Auth::User
|
187
|
+
|
188
|
+
protected
|
189
|
+
# Subclasses can override this to use some other authentication
|
190
|
+
# mechanism (LDAP or what have you).
|
191
|
+
def authenticate(user, password) # :doc:
|
192
|
+
user.hashed_password == Crypt.salt_password(user.salt, password)
|
193
|
+
end
|
194
|
+
private
|
195
|
+
def registration_error(error)
|
196
|
+
redirect URI.escape("#{register_url}?error=#{error}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|