Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

added all files

  • Loading branch information...
commit 06c3581517bc230b097a5602ab5e3d6029b6caa0 1 parent e6a0c9a
@Shogun147 authored
View
5 README.md
@@ -1,4 +1,3 @@
-Katana-auth
-===========
+# [Auth](http://github.com/Shogun147/Katana-Auth)
-Base authorization and authentication module with roles and rights
+Base authorization and authentication module with roles and rights
View
22 auth.js
@@ -0,0 +1,22 @@
+var User = require('./user');
+
+Class('Katana.Module.Auth', {
+ isa: Katana.Core.Module,
+
+ have: {
+ name: 'auth',
+ user: User
+ },
+
+ methods: {}
+});
+
+module.exports = new Katana.Module.Auth;
+
+App.on('request', function(Request, Response, callback) {
+ new User(Request.session, function(user) {
+ Request.user = user;
+
+ callback();
+ });
+});
View
68 config/development/auth.js
@@ -0,0 +1,68 @@
+module.exports = {
+ store: 'mongoose',
+
+ allow_registration: true,
+ accounts_per_email: 1,
+ accounts_per_ip: 5,
+ password_case_sensitive: false,
+ login_on_signup: true,
+
+ username_pattern: new RegExp('^[-_a-z0-9]{3,25}$', 'i'),
+ salt: '12345',
+
+ allow_username: true,
+ allow_email: true,
+ attempts_limit: 5,
+ attempts_expire: 60*60*24,
+
+ password_recovery: true,
+ password_recovery_expire: 60*60*24,
+
+ domain: 'http://localhost:8000/',
+
+ login_url: '/',
+ register_url: '/',
+ logout_url: '/',
+
+ email_confirmation: false,
+ email_confirmation_expire: 60*60*24*7,
+
+ email: {
+ smtp: {
+ service: 'Gmail',
+ username: 'gmail@email',
+ password: 'password'
+ },
+
+ from: 'from@email'
+ },
+
+ register_email: {
+ subject: 'Welcome to our site!',
+ text: 'COPY AND OPEN THIS LINK IN BROWSER TO CONFIRM YOUR EMAIL: [domain]account/actions/confirm_email/[username]/[key]',
+ html: '<a href="[domain]account/actions/confirm_email/[username]/[key]"><h3>CLICK THIS LINK TO CONFIRM YOUR EMAIL</h3></a>'
+ // template: 'email_templates/register'
+ },
+
+ new_password_request_email: {
+ subject: 'Password recovery',
+ text: 'COPY AND OPEN THIS LINK IN BROWSER TO GENERATE NEW PASSWORD: [domain]account/actions/generate_new_password/[username]/[key]',
+ html: '<a href="[domain]account/actions/generate_new_password/[username]/[key]"><h3>CLICK THIS LINK TO GENERATE NEW PASSWORD</h3></a>'
+ // template: 'email_templates/password_recovery'
+ },
+
+ new_password_email: {
+ subject: 'New password',
+ text: 'THIS IS YOUR NEW PASSWORD, PLEASE CHANGE IT AFTER LOGIN: [password]',
+ html: '<h3>THIS IS YOUR NEW PASSWORD, PLEASE CHANGE IT AFTER LOGIN: [password]</h3>'
+ // template: 'email_templates/new_password'
+ }
+}
+
+var App = global.App;
+
+var Session = App.Config().session;
+
+Session.defaults.logged_in = false;
+Session.defaults.user_id = null;
+
View
10 config/development/routing.js
@@ -0,0 +1,10 @@
+module.exports = {
+ route: {
+ controller: 'auth',
+ action: 'index'
+ },
+
+ routes: [
+
+ ]
+}
View
122 controllers/actions.js
@@ -0,0 +1,122 @@
+Class('Auth_Actions_Controller', {
+ methods: {
+ index: function(Response, Request) {
+ Response.redirect();
+ },
+
+ login: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ return (Request.is_ajax ? Response.send({ error: null }) : Response.redirect());
+ }
+
+ var data = Request.data;
+
+ User.login(data.username, data.password, Request.client.ip, function(error) {
+ Request.is_ajax ? Response.send({ error: error }) : Response.redirect();
+ });
+ },
+
+ logout: function(Response, Request) {
+ var User = Request.user;
+
+ User.logout();
+
+ Response.redirect();
+ },
+
+ register: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ Request.is_ajax ? Response.send({ error: null }) : Response.redirect();
+ }
+
+ var data = Request.data;
+
+ User.register(data.username, data.password, data.email, Request.client.ip, function(error) {
+ Request.is_ajax ? Response.send({ error: error }) : Response.redirect();
+ });
+ },
+
+ confirm_email: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ return Response.redirect();
+ }
+
+ var username = Request.arguments[0];
+ var key = Request.arguments[1];
+
+ User.confirm_email(username, key, function(error) {
+ console.log(error || 'email_confirmed')
+ });
+ },
+
+ recover_password: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ return Response.redirect();
+ }
+
+ Response.send('recover_password');
+ },
+
+ generate_new_password: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ return Response.redirect();
+ }
+
+ Response.send('generate_new_password');
+ },
+
+ _404: function(Response) {
+ Response.redirect();
+ }
+ }
+});
+
+module.exports = new Auth_Actions_Controller;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
View
25 controllers/auth.js
@@ -0,0 +1,25 @@
+Class('Auth_Auth_Controller', {
+ methods: {
+ login: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ return Response.redirect();
+ }
+
+ Response.render('auth:login');
+ },
+
+ signup: function(Response, Request) {
+ var User = Request.user;
+
+ if (User.logged_in()) {
+ return Response.redirect();
+ }
+
+ Response.render('auth:register');
+ }
+ }
+});
+
+module.exports = new Auth_Auth_Controller;
View
40 models/user.js
@@ -0,0 +1,40 @@
+var config = App.Config('auth:');
+
+var Mongoose = App.Store(config.store);
+var Schema = require('mongoose').Schema;
+
+var User = new Schema({
+ username: { type: String, required: true, index: { unique: true } },
+ password: { type: String, required: true },
+ email: { type: String, index: { unique: (config.accounts_per_email === 1) } },
+ class: String,
+ roles: [{ type: Schema.ObjectId, ref: 'roles' }],
+ status: Number,
+ signup_time: { type: Date },
+ signup_ip: String,
+ online: { type: Boolean, default: false },
+ last_login: Date,
+ last_action: Date,
+ last_login_ip: String,
+ np_request_key: String,
+ np_request_time: Date,
+ email_confirmed: Boolean,
+ banned: Boolean,
+ avatar: String
+});
+
+module.exports = Mongoose.model('users', User);
+
+var Role = new Schema({
+ name: { type: String, index: { unique: true } }
+});
+
+Mongoose.model('roles', Role);
+
+var Right = new Schema({
+ name: { type: String, index: { unique: true } },
+ roles: [{ type: Schema.ObjectId, ref: 'roles' }]
+});
+
+Mongoose.model('rights', Right);
+
View
30 package.json
@@ -0,0 +1,30 @@
+{
+ "name": "auth",
+ "description": "Basic authorization and authentication module with roles and rights",
+ "version": "0.1.0",
+
+ "author": "Shogun <Shogun147@gmail.com> (http://github.com/Shogun147)",
+ "contributors": [
+ { "name": "Shogun", "email": "Shogun147@gmail.com" }
+ ],
+
+ "keywords": ["auth", "authorization", "authentication", "roles", "rights", "users"],
+
+ "homepage": "http://katanajs.com/modules/auth (comming soon)",
+
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/Shogun147/Katana-Auth.git",
+ "web": "https://github.com/Shogun147/Katana-Auth"
+ },
+
+ "bugs": {
+ "url": "https://github.com/Shogun147/Katana-Auth/issues"
+ },
+
+ "dependencies": {
+
+ },
+
+ "main": "./auth"
+}
View
418 user.js
@@ -0,0 +1,418 @@
+var config = App.Config('auth:');
+
+var Mongoose = App.Store(config.store);
+var Users = App.Model('auth:user');
+var Roles = Mongoose.model('roles');
+var Rights = Mongoose.model('rights');
+
+var View = App.View;
+var Utils = App.Utils;
+
+var hmac = Utils.hmac;
+var md5 = Utils.md5;
+var check = Utils.validator;
+var sanitize = Utils.sanitize;
+var rand_str = Utils.rand_str;
+var merge = Utils.merge;
+
+var Mail = App.Module('mailer');
+
+Class('Katana.Module.Auth.User', {
+ have: {
+ id: 0, data: {},
+ roles: [], rights: [],
+ session: null
+ },
+
+ methods: {
+ BUILD: function(session, callback) {
+ return {
+ session: session,
+ callback: callback
+ }
+ },
+
+ initialize: function(args) {
+ var User = this;
+
+ var Session = args.session;
+ var callback = args.callback;
+
+ if (Session.get('logged_in')) {
+ Users.findOne({ _id: Session.get('user_id') }, function(error, user) {
+ if (error) { return callback(User); }
+
+ if (user !== null) {
+ User.id = user._id;
+ User.data = user;
+
+ Roles.distinct('name', { _id: { $in: user.roles } }, function(error, roles) {
+ if (!error && roles!==null) {
+ User.roles = roles;
+ }
+
+ Rights.distinct('name', { roles: { $in: user.roles } }, function(error, rights) {
+ if (!error && rights!==null) {
+ User.rights = rights;
+ }
+
+ callback(User);
+ });
+ });
+ } else {
+ Session.set('logged_in', false);
+ Session.set('user_id', 0);
+
+ callback(User);
+ }
+ });
+ } else {
+ callback(User);
+ }
+ },
+
+ login: function(username, password, ip, callback) {
+ var User = this;
+ var Session = User.session;
+
+ username = sanitize(username).trim();
+ password = sanitize(password).trim();
+
+ if (!config.password_case_sensitive) { password = password.toLowerCase(); }
+
+ var is_email = check(username, 'isEmail');
+
+ if (!is_email && !config.allow_username) { return callback('username_not_allowed'); }
+ if (is_email && !config.allow_email) { return callback('email_not_allowed'); }
+
+ if (!is_email && !config.username_pattern.test(username)) { return callback('invalid_username'); }
+ if (password == '') { return callback('password_not_set'); }
+
+ var data = {};
+ var field = is_email ? 'email' : 'username';
+
+ data[field] = { $regex : new RegExp('^'+ username +'$', "i") };
+
+ Users.findOne(data, function(error, user) {
+ if (error) { return callback('error'); }
+
+ if (user !== null) {
+ if (user.password === hmac(password, config.salt)) {
+
+ if (config.email_confirmation && !user.email_confirmed) { return callback('email_not_confirmed'); }
+
+ if (user.banned) { return callback('banned'); }
+
+ User.id = user._id;
+ User.data = user;
+
+ Session.set('logged_in', true);
+ Session.set('user_id', User.id);
+
+ user.last_login = Date.now();
+ user.last_login_ip = ip;
+
+ user.save(function(error) {
+ if (error) { return callback('update_error'); }
+
+ callback();
+ });
+ } else {
+ return callback('password_not_match');
+ }
+ } else {
+ return callback('not_found');
+ }
+ });
+ },
+
+ register: function(username, password, email, ip, callback) {
+ username = sanitize(username).trim();
+ password = sanitize(password).trim();
+ email = sanitize(email).trim();
+
+ if (!config.password_case_sensitive) { password = password.toLowerCase(); }
+
+ if (!config.username_pattern.test(username)) { return callback('invalid_username') }
+
+ if (!check(email, 'isEmail')) { return callback('invalid_email'); }
+
+ if (password == '') { return callback('password_not_set'); }
+
+ Users.count({ username: { $regex : new RegExp('^'+ username +'$', "i") } }, function(error, count) {
+ if (error) { return callback('error'); }
+
+ if (count) { return callback('username_not_available'); }
+
+ Users.count({ email: { $regex : new RegExp('^'+ email +'$', "i") } }, function(error, count) {
+ if (error) { return callback('error'); }
+
+ if (count >= config.accounts_per_email) { return callback('accounts_per_email_exceed'); }
+
+ Users.count({ ip: ip }, function(error, count) {
+ if (error) { return callback('error'); }
+
+ if (count >= config.accounts_per_ip) { return callback('accounts_per_ip_exceed'); }
+
+ var User = new Users();
+
+ User.username = username;
+ User.password = hmac(password, config.salt);
+ User.email = email;
+ User.email_confirmed = !config.email_confirmation;
+ User.signup_ip = ip;
+ User.signup_time = Date.now();
+ User.avatar = 'http://www.gravatar.com/avatar/'+ md5(User.email.toLowerCase()) +'?s=20';
+
+ User.save(function(error) {
+ if (error) { return callback('save_error'); }
+
+ if (config.email_confirmation) {
+ var key = hmac(User.username +':'+ User.email, config.salt);
+
+ var options = merge({}, config.email);
+
+ options.to = User.email;
+
+ merge(options, config.register_email);
+
+ var fields = {
+ username: User.username, email: User.email, password: password,
+ ip: ip, time: User.signup_time, key: key
+ };
+
+ for (field in fields) {
+ options.subject.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ options.text.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ options.html.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ }
+
+ if (options.template) {
+ options.html = View.render(options.template, fields);
+ }
+
+ Mail.send(options, function(error, results) {
+ if (error) { return callback('error_sending_email'); }
+
+ callback();
+ });
+ } else {
+ callback();
+ }
+ });
+ });
+ });
+ });
+ },
+
+ confirm_email: function(username, key, callback) {
+ username = sanitize(username).trim();
+ key = sanitize(key).trim();
+
+ if (!config.username_pattern.test(username)) { return callback('invalid_username') }
+
+ Users.findOne({ username: { $regex : new RegExp('^'+ username +'$', "i") } }, ['username', 'email'], function(error, user) {
+ if (error) { return callback('error'); }
+
+ if (user === null) { return callback('user_not_found'); }
+
+ if (key !== hmac(user.username +':'+ user.email, config.salt)) {
+ return callback('key_not_match');
+ }
+
+ // Email confirmation expiration, need random string insteed of just username + email hash
+ // if ((user.signup_time + config.email_configuration_expire) < Date.now()) { return callback('email_confirmation_expired'); }
+
+ user.email_confirmed = true;
+
+ user.save(function(error) {
+ if (error) { return callback('save_error'); }
+
+ callback();
+ });
+ });
+ },
+
+ request_new_password: function(username, email, callback) {
+ username = sanitize(username).trim();
+ email = sanitize(email).trim();
+
+ if (!config.password_recovery) { return callback('no_allowed'); }
+
+ if (!config.username_pattern.test(username)) { return callback('invalid_username'); }
+
+ if (!check(email, 'isEmail')) { return callback('invalid_email'); }
+
+ Users.findOne({ username: { $regex : new RegExp('^'+ username +'$', "i") }, email: { $regex : new RegExp('^'+ email +'$', "i") } }, function(error, user) {
+ if (error) { return callback('error'); }
+
+ if (user === null) { return callback('user_not_found'); }
+
+ var key = rand_str(32);
+
+ user.np_request_key = key;
+ user.np_request_time = Date.now();
+
+ user.save(function(error) {
+ if (error) { return callback('save_error'); }
+
+ var options = merge({}, config.email);
+
+ options.to = user.email;
+
+ merge(options, config.new_password_request_email);
+
+ var fields = {
+ username: user.username, email: user.email,
+ time: user.np_request_time, key: user.np_request_key
+ };
+
+ for (field in fields) {
+ options.subject.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ options.text.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ options.html.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ }
+
+ if (options.template) {
+ options.html = View.render(options.template, fields);
+ }
+
+ Mail.send(options, function(error, results) {
+ if (error) { return callback('error_sending_email'); }
+
+ callback();
+ });
+ });
+ });
+ },
+
+ check_password_reset: function(username, key, callback) {
+ username = sanitize(username).trim();
+
+ if (!config.username_pattern.test(username)) { return callback('invalid_username'); }
+
+ if (!/^[a-z0-9]{32}$/i.test(key)) { return callback('invalid_key'); }
+
+ Users.findOne({ username: { $regex : new RegExp('^'+ username +'$', "i") } }, function(error, user) {
+ if (error) { return callback('error'); }
+
+ if (user === null) { return callback('user_not_found'); }
+
+ if ((user.np_request_time + config.password_recovery_expire) < Date.now()) { return callback('key_expired'); }
+
+ if (user.np_request_key !== key) { return callback('key_not_match'); }
+
+ return callback();
+ });
+ },
+
+ generate_new_password: function(username, key, callback) {
+ username = sanitize(username).trim();
+
+ if (!config.username_pattern.test(username)) { return callback('invalid_username'); }
+
+ if (!/^[a-z0-9]{32}$/i.test(key)) { return callback('invalid_key'); }
+
+ Users.findOne({ username: { $regex : new RegExp('^'+ username +'$', "i") } }, function(error, user) {
+ if (error) { return callback('error'); }
+
+ if (user === null) { return callback('user_not_found'); }
+
+ if (user.np_request_key !== key) { return callback('key_not_match'); }
+
+ if ((user.np_request_time + config.password_recovery_expire) < Date.now()) { return callback('key_expired'); }
+
+ var new_password = rand_str();
+
+ user.password = hmac(new_password, config.salt);
+
+ user.save(function(error) {
+ if (error) { return callback('update_error'); }
+
+ var options = merge({}, config.email);
+
+ options.to = User.email;
+
+ merge(options, config.new_password_email);
+
+ var fields = {
+ username: user.username, email: user.email, password: new_password
+ };
+
+ for (field in fields) {
+ options.subject.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ options.text.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ options.html.replace(new RegExp('/\['+field+'\]/ig'), fields[field]);
+ }
+
+ if (options.template) {
+ options.html = View.render(options.template, fields);
+ }
+
+ Mail.send(options, function(error, results) {
+ if (error) { return callback('error_sending_email'); }
+
+ callback();
+ });
+ });
+ });
+ },
+
+ has_role: function(role) {
+ var User = this;
+
+ var roles = Array.slice.call(arguments);
+
+ var len = roles.length;
+
+ if (len > 1) {
+ for (var i=0; i<len; i++) {
+ if (User.roles.indexOf(roles[i]) === -1) { return false; }
+ }
+
+ return true;
+ }
+
+ return (User.roles.indexOf(role) !== -1);
+ },
+
+ can: function(right) {
+ var User = this;
+
+ var rights = Array.slice.call(arguments);
+
+ var len = rights.length;
+
+ if (len > 1) {
+ for (var i=0; i<len; i++) {
+ if (User.rights.indexOf(rights[i]) === -1) { return false; }
+ }
+
+ return true;
+ }
+
+ return (User.rights.indexOf(right) !== -1);
+ },
+
+ logged_in: function() {
+ return this.session.get('logged_in');
+ },
+
+ logout:function() {
+ this.id = 0;
+ this.data = {};
+ this.roles = [];
+ this.rights = [];
+
+ this.session.create();
+ },
+
+ get: function(name) {
+ if (!name) { return this.data; }
+
+ return this.data[name];
+ }
+ }
+});
+
+module.exports = Katana.Module.Auth.User;
View
5 views/login.html
@@ -0,0 +1,5 @@
+<form action="/account/actions/login" method="post">
+ Username: <input type="text" name="username"><br>
+ Password: <input type="password" name="password"><br>
+ <input type="submit" value="Login">
+</form>
View
6 views/register.html
@@ -0,0 +1,6 @@
+<form action="/account/actions/register" method="post">
+ Username: <input type="text" name="username"><br>
+ Email: <input type="text" name="email"><br>
+ Password: <input type="password" name="password"><br>
+ <input type="submit" value="Signup">
+</form>
Please sign in to comment.
Something went wrong with that request. Please try again.