Repository: cloudspace/angular_devise
Branch: master
Commit: 8936a4c31ad1
Files: 14
Total size: 77.3 KB
Directory structure:
gitextract_l6p1sy_y/
├── Gruntfile.js
├── LICENSE.md
├── README.md
├── bower.json
├── karma.conf.js
├── lib/
│ ├── devise-min.js
│ └── devise.js
├── package.json
├── src/
│ ├── 401.js
│ ├── auth.js
│ └── build/
│ └── devise.js
└── test/
├── devise.js
└── spec/
├── 401.js
└── auth.js
================================================
FILE CONTENTS
================================================
================================================
FILE: Gruntfile.js
================================================
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
meta: {
version: '<%= pkg.version %>',
banner:
'// AngularDevise\n' +
'// -------------------\n' +
'// v<%= pkg.version %>\n' +
'//\n' +
'// Copyright (c)<%= grunt.template.today("yyyy") %> Justin Ridgewell\n' +
'// Distributed under MIT license\n' +
'//\n' +
'// https://github.com/cloudspace/angular_devise\n' +
'\n'
},
preprocess: {
build: {
files: {
'lib/devise.js' : 'src/build/devise.js'
}
}
},
uglify : {
options: {
banner: "<%= meta.banner %>"
},
core : {
src : 'lib/devise.js',
dest : 'lib/devise-min.js',
}
},
jshint: {
options: {
jshintrc : '.jshintrc'
},
devise : [ 'src/*.js' ],
test : [ 'test/*.js', 'test/specs/*.js' ],
},
plato: {
devise : {
src : 'src/*.js',
dest : 'reports',
options : {
jshint : false
}
}
},
ngAnnotate: {
options: {
singleQuotes: true
},
dist: {
files: {
'lib/devise.js': ['lib/devise.js']
}
}
},
karma: {
options: {
configFile: 'karma.conf.js',
browsers: ['PhantomJS']
},
unit: {
},
continuous: {
singleRun: false
}
}
});
require('load-grunt-tasks')(grunt);
require('time-grunt')(grunt);
// Default task.
grunt.registerTask('lint-test', 'jshint:test');
grunt.registerTask('test', function(type) {
type = type || 'unit';
grunt.task.run('karma:' + type);
});
grunt.registerTask('travis', ['jshint:devise', 'karma']);
grunt.registerTask('build', ['default', 'preprocess', 'ngAnnotate', 'uglify']);
grunt.registerTask('default', ['jshint:devise', 'test']);
};
================================================
FILE: LICENSE.md
================================================
Copyright (c) 2013 Justin Ridgewell
===================================
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
AngularDevise [](http://travis-ci.org/cloudspace/angular_devise)
=============
A small AngularJS Service to interact with Devise Authentication.
Requirements
------------
This service requires Devise to respond to JSON. To do that, simply add
```ruby
# config/application.rb
module RailsApp
class Application < Rails::Application
# ...
config.to_prepare do
DeviseController.respond_to :html, :json
end
end
end
```
Additionally, if you have [CSRF Forgery
Protection](http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html)
enabled for your controller actions, you will also need to include the
`X-CSRF-TOKEN` header with the token provided by rails. The easiest way
to include this is to follow this post:
[angular_rails_csrf](http://stackoverflow.com/questions/14734243/rails-csrf-protection-angular-js-protect-from-forgery-makes-me-to-log-out-on).
Downloading
-----------
AngularDevise is registered as `angular-devise` in
[bower](http://sindresorhus.com/bower-components/#!/search/angular-devise).
```bash
bower install --save angular-devise
```
You can then use the main file at `angular-devise/lib/devise-min.js`.
Rails Assets
------------
To get AngularDevise via [Rails Assets](https://rails-assets.org/) add to your Gemfile:
```ruby
source "https://rails-assets.org" do
gem "rails-assets-angular-devise"
end
```
Then `bundle`. Finally, to require the JS:
```js
//= require angular-devise
```
Usage
-----
Just register `Devise` as a dependency for your module. Then, the `Auth`
service will be available for use.
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
// Configure Auth service with AuthProvider
}).
controller('myCtrl', function(Auth) {
// Use your configured Auth service.
});
```
### Auth.currentUser()
`Auth.currentUser()` returns a promise that will be resolved into the
currentUser. There are three possible outcomes:
1. Auth has authenticated a user, and will resolve with that user.
2. Auth has not authenticated a user but the server has a previously
authenticated session, Auth will attempt to retrieve that session
and resolve with its user. Then, a `devise:new-session` event will
be broadcast with the current user as the argument.
3. Neither Auth nor the server has an authenticated session, and a
rejected promise will be returned. (see [Interceptor](#interceptor)
for for custom handling.)
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
Auth.currentUser().then(function(user) {
// User was logged in, or Devise returned
// previously authenticated session.
console.log(user); // => {id: 1, ect: '...'}
}, function(error) {
// unauthenticated error
});
});
```
#### Auth._currentUser
`Auth._currentUser` will be either `null` or the currentUser's object
representation. It is not recommended to directly access
`Auth._currentUser`, but instead use
[Auth.currentUser()](#authcurrentuser).
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
console.log(Auth._currentUser); // => null
// Log in user...
console.log(Auth._currentUser); // => {id: 1, ect: '...'}
});
```
### Auth.isAuthenticated()
`Auth.isAuthenticated()` is a helper method to determine if a
currentUser is logged in with Auth.
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
console.log(Auth.isAuthenticated()); // => false
// Log in user...
console.log(Auth.isAuthenticated()); // => true
});
```
### Auth.login(creds, config)
Use `Auth.login()` to authenticate with the server. Keep in mind,
credentials are sent in plaintext; use a SSL connection to secure them.
`creds` is an object which should contain any credentials needed to
authenticate with the server. `Auth.login()` will return a promise that
will resolve to the logged-in user. See
[Auth.parse(response)](#authparseresponse) to customize how the response
is parsed into a user.
Upon a successful login, two events will be broadcast, `devise:login` and
`devise:new-session`, both with the currentUser as the argument. New-Session will only
be broadcast if the user was logged in by `Auth.login({...})`. If the server
has a previously authenticated session, only the login event will be broadcast.
Pass any additional config options you need to provide to `$http` with
`config`.
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
var credentials = {
email: 'user@domain.com',
password: 'password1'
};
var config = {
headers: {
'X-HTTP-Method-Override': 'POST'
}
};
Auth.login(credentials, config).then(function(user) {
console.log(user); // => {id: 1, ect: '...'}
}, function(error) {
// Authentication failed...
});
$scope.$on('devise:login', function(event, currentUser) {
// after a login, a hard refresh, a new tab
});
$scope.$on('devise:new-session', function(event, currentUser) {
// user logged in by Auth.login({...})
});
});
```
By default, `login` will POST to '/users/sign_in.json' using the
resource name `user`. The path, HTTP method, and resource name used to
login are configurable using:
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
AuthProvider.loginPath('path/on/server.json');
AuthProvider.loginMethod('GET');
AuthProvider.resourceName('customer');
});
```
### Auth.logout()
Use `Auth.logout()` to de-authenticate from the server. `Auth.logout()`
returns a promise that will be resolved to the old currentUser. Then a
`devise:logout` event will be broadcast with the old currentUser as the argument.
Pass any additional config options you need to provide to `$http` with
`config`.
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
var config = {
headers: {
'X-HTTP-Method-Override': 'DELETE'
}
};
// Log in user...
// ...
Auth.logout(config).then(function(oldUser) {
// alert(oldUser.name + "you're signed out now.");
}, function(error) {
// An error occurred logging out.
});
$scope.$on('devise:logout', function(event, oldCurrentUser) {
// ...
});
});
```
By default, `logout` will DELETE to '/users/sign_out.json'. The path and
HTTP method used to logout are configurable using:
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
AuthProvider.logoutPath('path/on/server.json');
AuthProvider.logoutMethod('GET');
});
```
### Auth.parse(response)
This is the method used to parse the `$http` response into the appropriate
user object. By default, it simply returns `response.data`. This can be
customized either by specifying a parse function during configuration:
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
// Customize user parsing
// NOTE: **MUST** return a truth-y expression
AuthProvider.parse(function(response) {
return response.data.user;
});
});
```
or by directly overwriting it, perhaps when writing a custom version of
the Auth service which depends on another service:
```javascript
angular.module('myModule', ['Devise']).
factory('User', function() {
// Custom user factory
}).
factory('CustomAuth', function(Auth, User) {
Auth['parse'] = function(response) {
return new User(response.data);
};
return Auth;
});
```
### Auth.register(creds)
Use `Auth.register()` to register and authenticate with the server. Keep
in mind, credentials are sent in plaintext; use a SSL connection to
secure them. `creds` is an object that should contain any credentials
needed to register with the server. `Auth.register()` will return a
promise that will resolve to the registered user. See
[Auth.parse(response)](#authparseresponse) to customize how the response
is parsed into a user. Then a `devise:new-registration` event will be
broadcast with the user object as the argument.
Pass any additional config options you need to provide to `$http` with
`config`.
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
var credentials = {
email: 'user@domain.com',
password: 'password1',
password_confirmation: 'password1'
};
var config = {
headers: {
'X-HTTP-Method-Override': 'POST'
}
};
Auth.register(credentials, config).then(function(registeredUser) {
console.log(registeredUser); // => {id: 1, ect: '...'}
}, function(error) {
// Registration failed...
});
$scope.$on('devise:new-registration', function(event, user) {
// ...
});
});
```
By default, `register` will POST to '/users.json' using the resource
name `user`. The path, HTTP method, and resource name used to register
are configurable using:
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
AuthProvider.registerPath('path/on/server.json');
AuthProvider.registerMethod('GET');
AuthProvider.resourceName('customer');
});
```
### Auth.sendResetPasswordInstructions(creds)
Use `Auth.sendResetPasswordInstructions()` to send reset password mail to user. Keep
in mind, credentials are sent in plaintext; use a SSL connection to
secure them. `creds` is an object that should contain the email associated with the user.
`Auth.sendResetPasswordInstructions()` will return a promise with no params.
Then a `devise:send-reset-password-instructions-successfully` event will be broadcast.
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
var parameters = {
email: 'user@domain.com'
};
Auth.sendResetPasswordInstructions(parameters).then(function() {
// Sended email if user found otherwise email not sended...
});
$scope.$on('devise:send-reset-password-instructions-successfully', function(event) {
// ...
});
});
```
By default, `sendResetPasswordInstructions` will POST to '/users/password.json'. The path and HTTP
method used to send the reset password instructions are configurable using:
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
AuthProvider.sendResetPasswordInstructionsPath('path/on/server.json');
AuthProvider.sendResetPasswordInstructionsMethod('POST');
});
```
### Auth.resetPassword(creds)
Use `Auth.resetPassword()` to reset user password. Keep
in mind, credentials are sent in plaintext; use a SSL connection to
secure them. `creds` is an object that should contain password, password_confirmation and reset_password_token.
`Auth.resetPassword()` will return a
promise that will resolve to the new user data. See
[Auth.parse(response)](#authparseresponse) to customize how the response
is parsed into a user. Then a `devise:reset-password-successfully` event will be broadcast.
```javascript
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
var parameters = {
password: 'new_password',
password_confirmation: 'new_password',
reset_password_token: 'reset_token',
};
Auth.resetPassword(parameters).then(function(new_data) {
console.log(new_data); // => {id: 1, ect: '...'}
}, function(error) {
// Reset password failed...
});
$scope.$on('devise:reset-password-successfully', function(event) {
// ...
});
});
```
By default, `resetPassword` will PUT to '/users/password.json'. The path and HTTP
method used to reset password are configurable using:
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
AuthProvider.resetPasswordPath('path/on/server.json');
AuthProvider.resetPasswordMethod('PUT');
});
```
Interceptor
-----------
AngularDevise comes with a [$http
Interceptor](http://docs.angularjs.org/api/ng.$http#description_interceptors)
that may be enabled using the `interceptAuth` config. Its purpose is to
listen for `401 Unauthorized` responses and give you the ability to
seamlessly recover. When it catches a 401, it will:
1. create a deferred
2. broadcast a `devise:unauthorized` event passing:
- the ajax response
- the deferred
3. return the deferred's promise
Since the deferred is passed to the `devise:unauthorized` event, you are
free to resolve it (and the request) inside of the event listener. For
instance:
```javascript
angular.module('myModule', []).
controller('myCtrl', function($scope, Auth, $http) {
// Guest user
// Catch unauthorized requests and recover.
$scope.$on('devise:unauthorized', function(event, xhr, deferred) {
// Disable interceptor on _this_ login request,
// so that it too isn't caught by the interceptor
// on a failed login.
var config = {
interceptAuth: false
};
// Ask user for login credentials
Auth.login(credentials, config).then(function() {
// Successfully logged in.
// Redo the original request.
return $http(xhr.config);
}).then(function(response) {
// Successfully recovered from unauthorized error.
// Resolve the original request's promise.
deferred.resolve(response);
}, function(error) {
// There was an error logging in.
// Reject the original request's promise.
deferred.reject(error);
});
});
// Request requires authorization
// Will cause a `401 Unauthorized` response,
// that will be recovered by our listener above.
$http.delete('/users/1', {
interceptAuth: true
}).then(function(response) {
// Deleted user 1
}, function(error) {
// Something went wrong.
});
});
```
The Interceptor can be enabled globally or on a per-request basis using the
`interceptAuth` setting on the AuthIntercept provider.
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthInterceptProvider) {
// Intercept 401 Unauthorized everywhere
AuthInterceptProvider.interceptAuth(true);
}).
controller('myCtrl', function($http) {
// Disable per-request
$http({
url: '/',
interceptAuth: false,
// ...
});
});
```
AuthProvider
------------
By default, AngularDevise uses the following HTTP methods/paths:
| Method | HTTP Method | HTTP Path |
| -------- | ----------- | -------------------- |
| login | POST | /users/sign_in.json |
| logout | DELETE | /users/sign_out.json |
| register | POST | /users.json |
| sendResetPasswordInstructions | POST | /users/password.json |
| resetPassword | POST | /users/password.json |
All credentials will be under the `users` namespace, and the following
parse function will be used to parse the response:
```javascript
function(response) {
return response.data;
};
```
All of these can be configured using a `.config` block in your module.
```javascript
angular.module('myModule', ['Devise']).
config(function(AuthProvider, AuthInterceptProvider) {
// Customize login
AuthProvider.loginMethod('GET');
AuthProvider.loginPath('/admins/login.json');
// Customize logout
AuthProvider.logoutMethod('POST');
AuthProvider.logoutPath('/user/logout.json');
// Customize register
AuthProvider.registerMethod('PATCH');
AuthProvider.registerPath('/user/sign_up.json');
// Customize the resource name data use namespaced under
// Pass false to disable the namespace altogether.
AuthProvider.resourceName('customer');
// Also you can change host URL for backend calls
// (for example if it's on another server than your angular app)
AuthProvider.baseUrl('http://localhost:3000');
// Customize user parsing
// NOTE: **MUST** return a truth-y expression
AuthProvider.parse(function(response) {
return response.data.user;
});
// Intercept 401 Unauthorized everywhere
// Enables `devise:unauthorized` interceptor
AuthInterceptProvider.interceptAuth(true);
});
```
Credits
-------
[](http://cloudspace.com/)
AngularDevise is maintained by [Cloudspace](http://cloudspace.com/), and
is distributed under the [MIT License](/LICENSE.md).
================================================
FILE: bower.json
================================================
{
"name": "AngularDevise",
"main": "lib/devise.js",
"ignore": [
"**/.*",
"node_modules",
"components",
"bower_components",
"spec",
"reports"
],
"author": {
"name": "Justin Ridgewell"
},
"dependencies": {
"angular": "^1.2.0"
},
"devDependencies": {
"angular-mocks": "^1.2.0",
"angular-scenario": "^1.2.0"
}
}
================================================
FILE: karma.conf.js
================================================
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
plugins : ['karma-jasmine', 'karma-phantomjs-launcher'],
// list of files / patterns to load in the browser
files: [
'test/support/angular/angular.js',
'test/support/angular-mocks/angular-mocks.js',
'test/devise.js',
'src/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
browsers: ['Chrome'],
singleRun: true
});
};
================================================
FILE: lib/devise-min.js
================================================
// AngularDevise
// -------------------
// v1.2.1
//
// Copyright (c)2016 Justin Ridgewell
// Distributed under MIT license
//
// https://github.com/cloudspace/angular_devise
!function(a){"use strict";var b=a.module("Devise",[]);b.provider("AuthIntercept",function(){var a=!1;this.interceptAuth=function(b){return a=!!b||void 0===b,this},this.$get=["$rootScope","$q",function(b,c){return{responseError:function(d){var e=d.config.interceptAuth;if(e=!!e||a&&void 0===e,e&&401===d.status){var f=c.defer();return b.$broadcast("devise:unauthorized",d,f),f.reject(d),f.promise}return c.reject(d)}}}]}).config(["$httpProvider",function(a){a.interceptors.push("AuthIntercept")}]),b.provider("Auth",function(){function b(b,c,d){var h={method:f[b].toLowerCase(),url:e[b]};return c&&(g?(h.data={},h.data[g]=c):h.data=c),a.extend(h,d),h}function c(b,c){a.forEach(b,function(a,d){this[d+c]=function(a){return void 0===a?b[d]:(b[d]=a,this)}},this)}function d(a){return function(){return a}}var e={login:"/users/sign_in.json",logout:"/users/sign_out.json",register:"/users.json",sendResetPasswordInstructions:"/users/password.json",resetPassword:"/users/password.json"},f={login:"POST",logout:"DELETE",register:"POST",sendResetPasswordInstructions:"POST",resetPassword:"PUT"},g="user",h=function(a){return a.data};c.call(this,f,"Method"),c.call(this,e,"Path"),this.resourceName=function(a){return void 0===a?g:(g=a,this)},this.parse=function(a){return"function"!=typeof a?h:(h=a,this)},this.$get=["$q","$http","$rootScope",function(a,c,e){function f(a){return j._currentUser=a,a}function g(){f(null),j._promise=null}function i(a){return function(b){return e.$broadcast("devise:"+a,b),b}}var j={_currentUser:null,parse:h,_promise:null,reset:function(){g(),j.currentUser()},login:function(a,d){var e=arguments.length>0,g=j.isAuthenticated();return a=a||{},c(b("login",a,d)).then(j.parse).then(f).then(function(a){return e&&!g?i("new-session")(a):a}).then(i("login"))},logout:function(a){var e=d(j._currentUser);return c(b("logout",void 0,a)).then(g).then(e).then(i("logout"))},register:function(a,d){return a=a||{},c(b("register",a,d)).then(j.parse).then(f).then(i("new-registration"))},sendResetPasswordInstructions:function(a){return a=a||{},c(b("sendResetPasswordInstructions",a)).then(j.parse).then(i("send-reset-password-instructions-successfully"))},resetPassword:function(a){return a=a||{},c(b("resetPassword",a)).then(j.parse).then(f).then(i("reset-password-successfully"))},currentUser:function(){return j.isAuthenticated()?a.when(j._currentUser):(null===j._promise&&(j._promise=j.login()),j._promise)},isAuthenticated:function(){return!!j._currentUser}};return j}]})}(angular);
================================================
FILE: lib/devise.js
================================================
(function(angular) {
'use strict';
var devise = angular.module('Devise', []);
devise.provider('AuthIntercept', function AuthInterceptProvider() {
/**
* Set to true to intercept 401 Unauthorized responses
*/
var interceptAuth = false;
// The interceptAuth config function
this.interceptAuth = function(value) {
interceptAuth = !!value || value === void 0;
return this;
};
this.$get = ['$rootScope', '$q', function($rootScope, $q) {
// Only for intercepting 401 requests.
return {
responseError: function(response) {
// Determine if the response is specifically disabling the interceptor.
var intercept = response.config.interceptAuth;
intercept = !!intercept || (interceptAuth && intercept === void 0);
if (intercept && response.status === 401) {
var deferred = $q.defer();
$rootScope.$broadcast('devise:unauthorized', response, deferred);
deferred.reject(response);
return deferred.promise;
}
return $q.reject(response);
}
};
}];
}).config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('AuthIntercept');
}]);
devise.provider('Auth', function AuthProvider() {
/**
* The default paths.
*/
var paths = {
login: '/users/sign_in.json',
logout: '/users/sign_out.json',
register: '/users.json',
sendResetPasswordInstructions: '/users/password.json',
resetPassword: '/users/password.json'
};
/**
* The default HTTP methods to use.
*/
var methods = {
login: 'POST',
logout: 'DELETE',
register: 'POST',
sendResetPasswordInstructions: 'POST',
resetPassword: 'PUT'
};
/**
* Default devise resource_name is 'user', can be set to any string.
* If it's falsey, it will not namespace the data.
*/
var resourceName = 'user';
/**
* The parsing function used to turn a $http
* response into a "user".
*
* Can be swapped with another parsing function
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.parse(function(response) {
* return new User(response.data);
* });
* });
*/
var _parse = function(response) {
return response.data;
};
// A helper function that will setup the ajax config
// and merge the data key if provided
function httpConfig(action, data, additionalConfig) {
var config = {
method: methods[action].toLowerCase(),
url: paths[action]
};
if (data) {
if (resourceName) {
config.data = {};
config.data[resourceName] = data;
} else {
config.data = data;
}
}
angular.extend(config, additionalConfig);
return config;
}
// A helper function to define our configure functions.
// Loops over all properties in obj, and creates a get/set
// method for [key + suffix] to set that property on obj.
function configure(obj, suffix) {
angular.forEach(obj, function(v, action) {
this[action + suffix] = function(param) {
if (param === undefined) {
return obj[action];
}
obj[action] = param;
return this;
};
}, this);
}
configure.call(this, methods, 'Method');
configure.call(this, paths, 'Path');
// The resourceName config function
this.resourceName = function(value) {
if (value === undefined) {
return resourceName;
}
resourceName = value;
return this;
};
// The parse configure function.
this.parse = function(fn) {
if (typeof fn !== 'function') {
return _parse;
}
_parse = fn;
return this;
};
// Creates a function that always
// returns a given arg.
function constant(arg) {
return function() {
return arg;
};
}
this.$get = ['$q', '$http', '$rootScope', function($q, $http, $rootScope) {
// Our shared save function, called
// by `then`s.
function save(user) {
service._currentUser = user;
return user;
}
// A reset that saves null for currentUser
function reset() {
save(null);
service._promise = null;
}
function broadcast(name) {
return function(data) {
$rootScope.$broadcast('devise:' + name, data);
return data;
};
}
var service = {
/**
* The Auth service's current user.
* This is shared between all instances of Auth
* on the scope.
*/
_currentUser: null,
/**
* The Auth service's parsing function.
* Defaults to the parsing function set in the provider,
* but may also be overwritten directly on the service.
*/
parse: _parse,
/**
* The Auth service's current promise
* This is shared between all instances of Auth
* on the scope.
*/
_promise: null,
/* reset promise and current_user, after call this method all
* xhr request will be reprocessed when they will be call
*/
reset: function(){
reset();
service.currentUser();
},
/**
* A login function to authenticate with the server.
* Keep in mind, credentials are sent in plaintext;
* use a SSL connection to secure them. By default,
* `login` will POST to '/users/sign_in.json'.
*
* The path and HTTP method used to login are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.loginPath('path/on/server.json');
* AuthProvider.loginMethod('GET');
* });
*
* @param {Object} [creds] A hash of user credentials.
* @param {Object} [config] Optional, additional config which
* will be added to http config for underlying
* $http.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
login: function(creds, config) {
var withCredentials = arguments.length > 0,
loggedIn = service.isAuthenticated();
creds = creds || {};
return $http(httpConfig('login', creds, config))
.then(service.parse)
.then(save)
.then(function(user) {
if (withCredentials && !loggedIn) {
return broadcast('new-session')(user);
}
return user;
})
.then(broadcast('login'));
},
/**
* A logout function to de-authenticate from the server.
* By default, `logout` will DELETE to '/users/sign_out.json'.
*
* The path and HTTP method used to logout are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.logoutPath('path/on/server.json');
* AuthProvider.logoutMethod('GET');
* });
* @param {Object} [config] Optional, additional config which
* will be added to http config for underlying
* $http.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
logout: function(config) {
var returnOldUser = constant(service._currentUser);
return $http(httpConfig('logout', undefined, config))
.then(reset)
.then(returnOldUser)
.then(broadcast('logout'));
},
/**
* A register function to register and authenticate
* with the server. Keep in mind, credentials are sent
* in plaintext; use a SSL connection to secure them.
* By default, `register` will POST to '/users.json'.
*
* The path and HTTP method used to login are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.registerPath('path/on/server.json');
* AuthProvider.registerMethod('GET');
* });
*
* @param {Object} [creds] A hash of user credentials.
* @param {Object} [config] Optional, additional config which
* will be added to http config for underlying
* $http.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
register: function(creds, config) {
creds = creds || {};
return $http(httpConfig('register', creds, config))
.then(service.parse)
.then(save)
.then(broadcast('new-registration'));
},
/**
* A function to send the reset password instructions to the
* user email.
* By default, `sendResetPasswordInstructions` will POST to '/users/password.json'.
*
* The path and HTTP method used to send instructions are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.sendResetPasswordInstructionsPath('path/on/server.json');
* AuthProvider.sendResetPasswordInstructionsMethod('POST');
* });
*
* @param {Object} [creds] A hash containing user email.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
sendResetPasswordInstructions: function(creds) {
creds = creds || {};
return $http(httpConfig('sendResetPasswordInstructions', creds))
.then(service.parse)
.then(broadcast('send-reset-password-instructions-successfully'));
},
/**
* A reset function to reset user password.
* By default, `resetPassword` will PUT to '/users/password.json'.
*
* The path and HTTP method used to reset password are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.resetPasswordPath('path/on/server.json');
* AuthProvider.resetPasswordMethod('POST');
* });
*
* @param {Object} [creds] A hash containing password, password_confirmation and reset_password_token.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
resetPassword: function(creds) {
creds = creds || {};
return $http(httpConfig('resetPassword', creds))
.then(service.parse)
.then(save)
.then(broadcast('reset-password-successfully'));
},
/**
* A helper function that will return a promise with the currentUser.
* Three different outcomes can happen:
* 1. Auth has authenticated a user, and will resolve with it
* 2. Auth has not authenticated a user but the server has an
* authenticated session, Auth will attempt to retrieve that
* session and resolve with its user.
* 3. Neither Auth nor the server has an authenticated session,
* and will reject with an unauthenticated error.
*
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
currentUser: function() {
if (service.isAuthenticated()) {
return $q.when(service._currentUser);
}
if(service._promise === null){
service._promise = service.login();
}
return service._promise;
},
/**
* A helper function to determine if a currentUser is present.
*
* @returns Boolean
*/
isAuthenticated: function(){
return !!service._currentUser;
}
};
return service;
}];
});
})(angular);
================================================
FILE: package.json
================================================
{
"name": "AngularDevise",
"version": "1.3.0",
"description": "A small AngularJS Service to interact with Devise Authentication.",
"main": "lib/devise.js",
"scripts": {
"test": "grunt test"
},
"repository": {
"type": "git",
"url": "https://github.com/cloudspace/angular_devise.git"
},
"keywords": [
"Angular",
"AngularJS",
"Devise"
],
"author": "Justin Ridgewell",
"license": "MIT",
"bugs": {
"url": "https://github.com/cloudspace/angular_devise/issues"
},
"dependencies": {},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.6.2",
"grunt-contrib-uglify": "~0.2.2",
"grunt-karma": "~0.12.1",
"grunt-ng-annotate": "~0.5.0",
"grunt-plato": "~0.2.1",
"grunt-preprocess": "~2.3.0",
"jasmine": "^2.4.1",
"karma": "^0.13.21",
"karma-jasmine": "^0.3.7",
"karma-phantomjs-launcher": "^1.0.0",
"load-grunt-tasks": "~0.2.0",
"phantomjs-prebuilt": "^2.1.4",
"time-grunt": "~0.2.1"
}
}
================================================
FILE: src/401.js
================================================
devise.provider('AuthIntercept', function AuthInterceptProvider() {
/**
* Set to true to intercept 401 Unauthorized responses
*/
var interceptAuth = false;
// The interceptAuth config function
this.interceptAuth = function(value) {
interceptAuth = !!value || value === void 0;
return this;
};
this.$get = function($rootScope, $q) {
// Only for intercepting 401 requests.
return {
responseError: function(response) {
// Determine if the response is specifically disabling the interceptor.
var intercept = response.config.interceptAuth;
intercept = !!intercept || (interceptAuth && intercept === void 0);
if (intercept && response.status === 401) {
var deferred = $q.defer();
$rootScope.$broadcast('devise:unauthorized', response, deferred);
deferred.reject(response);
return deferred.promise;
}
return $q.reject(response);
}
};
};
}).config(function($httpProvider) {
$httpProvider.interceptors.push('AuthIntercept');
});
================================================
FILE: src/auth.js
================================================
devise.provider('Auth', function AuthProvider() {
/**
* The default paths.
*/
var paths = {
login: '/users/sign_in.json',
logout: '/users/sign_out.json',
register: '/users.json',
sendResetPasswordInstructions: '/users/password.json',
resetPassword: '/users/password.json'
};
/**
* The default HTTP methods to use.
*/
var methods = {
login: 'POST',
logout: 'DELETE',
register: 'POST',
sendResetPasswordInstructions: 'POST',
resetPassword: 'PUT'
};
/**
* The default host URL.
*/
var baseUrl = '';
/**
* Default devise resource_name is 'user', can be set to any string.
* If it's falsey, it will not namespace the data.
*/
var resourceName = 'user';
/**
* The parsing function used to turn a $http
* response into a "user".
*
* Can be swapped with another parsing function
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.parse(function(response) {
* return new User(response.data);
* });
* });
*/
var _parse = function(response) {
return response.data;
};
// A helper function that will setup the ajax config
// and merge the data key if provided
function httpConfig(action, data, additionalConfig) {
var config = {
method: methods[action].toLowerCase(),
url: paths[action]
};
if (data) {
if (resourceName) {
config.data = {};
config.data[resourceName] = data;
} else {
config.data = data;
}
}
angular.extend(config, additionalConfig);
return config;
}
// A helper function to define our configure functions.
// Loops over all properties in obj, and creates a get/set
// method for [key + suffix] to set that property on obj.
function configure(obj, suffix) {
angular.forEach(obj, function(v, action) {
this[action + suffix] = function(param) {
if (param === undefined) {
return obj[action];
}
obj[action] = param;
return this;
};
}, this);
}
configure.call(this, methods, 'Method');
configure.call(this, paths, 'Path');
// The baseUrl config function
this.baseUrl = function(value) {
if (value === undefined) {
return baseUrl;
}
baseUrl = value;
return this;
};
// The resourceName config function
this.resourceName = function(value) {
if (value === undefined) {
return resourceName;
}
resourceName = value;
return this;
};
// The parse configure function.
this.parse = function(fn) {
if (typeof fn !== 'function') {
return _parse;
}
_parse = fn;
return this;
};
// Creates a function that always
// returns a given arg.
function constant(arg) {
return function() {
return arg;
};
}
this.$get = function($q, $http, $rootScope) {
// Our shared save function, called
// by `then`s.
function save(user) {
service._currentUser = user;
return user;
}
// A reset that saves null for currentUser
function reset() {
save(null);
service._promise = null;
}
function broadcast(name) {
return function(data) {
$rootScope.$broadcast('devise:' + name, data);
return data;
};
}
var service = {
/**
* The Auth service's current user.
* This is shared between all instances of Auth
* on the scope.
*/
_currentUser: null,
/**
* The Auth service's parsing function.
* Defaults to the parsing function set in the provider,
* but may also be overwritten directly on the service.
*/
parse: _parse,
/**
* The Auth service's current promise
* This is shared between all instances of Auth
* on the scope.
*/
_promise: null,
/* reset promise and current_user, after call this method all
* xhr request will be reprocessed when they will be call
*/
reset: function(){
reset();
service.currentUser();
},
/**
* A login function to authenticate with the server.
* Keep in mind, credentials are sent in plaintext;
* use a SSL connection to secure them. By default,
* `login` will POST to '/users/sign_in.json'.
*
* The path and HTTP method used to login are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.loginPath('path/on/server.json');
* AuthProvider.loginMethod('GET');
* });
*
* @param {Object} [creds] A hash of user credentials.
* @param {Object} [config] Optional, additional config which
* will be added to http config for underlying
* $http.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
login: function(creds, config) {
var withCredentials = arguments.length > 0,
loggedIn = service.isAuthenticated();
creds = creds || {};
return $http(httpConfig('login', creds, config))
.then(service.parse)
.then(save)
.then(function(user) {
if (withCredentials && !loggedIn) {
return broadcast('new-session')(user);
}
return user;
})
.then(broadcast('login'));
},
/**
* A logout function to de-authenticate from the server.
* By default, `logout` will DELETE to '/users/sign_out.json'.
*
* The path and HTTP method used to logout are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.logoutPath('path/on/server.json');
* AuthProvider.logoutMethod('GET');
* });
* @param {Object} [config] Optional, additional config which
* will be added to http config for underlying
* $http.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
logout: function(config) {
var returnOldUser = constant(service._currentUser);
return $http(httpConfig('logout', undefined, config))
.then(reset)
.then(returnOldUser)
.then(broadcast('logout'));
},
/**
* A register function to register and authenticate
* with the server. Keep in mind, credentials are sent
* in plaintext; use a SSL connection to secure them.
* By default, `register` will POST to '/users.json'.
*
* The path and HTTP method used to login are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.registerPath('path/on/server.json');
* AuthProvider.registerMethod('GET');
* });
*
* @param {Object} [creds] A hash of user credentials.
* @param {Object} [config] Optional, additional config which
* will be added to http config for underlying
* $http.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
register: function(creds, config) {
creds = creds || {};
return $http(httpConfig('register', creds, config))
.then(service.parse)
.then(save)
.then(broadcast('new-registration'));
},
/**
* A function to send the reset password instructions to the
* user email.
* By default, `sendResetPasswordInstructions` will POST to '/users/password.json'.
*
* The path and HTTP method used to send instructions are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.sendResetPasswordInstructionsPath('path/on/server.json');
* AuthProvider.sendResetPasswordInstructionsMethod('POST');
* });
*
* @param {Object} [creds] A hash containing user email.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
sendResetPasswordInstructions: function(creds) {
creds = creds || {};
return $http(httpConfig('sendResetPasswordInstructions', creds))
.then(service.parse)
.then(broadcast('send-reset-password-instructions-successfully'));
},
/**
* A reset function to reset user password.
* By default, `resetPassword` will PUT to '/users/password.json'.
*
* The path and HTTP method used to reset password are configurable
* using
*
* angular.module('myModule', ['Devise']).
* config(function(AuthProvider) {
* AuthProvider.resetPasswordPath('path/on/server.json');
* AuthProvider.resetPasswordMethod('POST');
* });
*
* @param {Object} [creds] A hash containing password, password_confirmation and reset_password_token.
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
resetPassword: function(creds) {
creds = creds || {};
return $http(httpConfig('resetPassword', creds))
.then(service.parse)
.then(save)
.then(broadcast('reset-password-successfully'));
},
/**
* A helper function that will return a promise with the currentUser.
* Three different outcomes can happen:
* 1. Auth has authenticated a user, and will resolve with it
* 2. Auth has not authenticated a user but the server has an
* authenticated session, Auth will attempt to retrieve that
* session and resolve with its user.
* 3. Neither Auth nor the server has an authenticated session,
* and will reject with an unauthenticated error.
*
* @returns {Promise} A $http promise that will be resolved or
* rejected by the server.
*/
currentUser: function() {
if (service.isAuthenticated()) {
return $q.when(service._currentUser);
}
if(service._promise === null){
service._promise = service.login();
}
return service._promise;
},
/**
* A helper function to determine if a currentUser is present.
*
* @returns Boolean
*/
isAuthenticated: function(){
return !!service._currentUser;
}
};
return service;
};
});
================================================
FILE: src/build/devise.js
================================================
(function(angular) {
'use strict';
var devise = angular.module('Devise', []);
// @include ../401.js
// @include ../auth.js
})(angular);
================================================
FILE: test/devise.js
================================================
var devise = angular.module('Devise', []);
================================================
FILE: test/spec/401.js
================================================
'use strict';
describe('Service: Devise.401', function () {
// load the service's module
beforeEach(module('Devise'));
var AuthInterceptProvider;
// load the service's module
beforeEach(module('Devise', function(_AuthInterceptProvider_) {
AuthInterceptProvider = _AuthInterceptProvider_;
}));
var $http, $httpBackend;
beforeEach(inject(function(_$http_, _$httpBackend_) {
$http = _$http_;
$httpBackend = _$httpBackend_;
}));
describe('responseError', function() {
beforeEach(function() {
$httpBackend.expect('GET', '/foo').respond(401);
});
describe('when interceptAuth is true', function() {
beforeEach(function() {
AuthInterceptProvider.interceptAuth();
});
afterEach(function() {
AuthInterceptProvider.interceptAuth(false);
});
it('can be disabled per request', inject(function ($rootScope) {
var callback = jasmine.createSpy('callback');
$rootScope.$on('devise:unauthorized', callback);
$http.get('/foo', { interceptAuth: false });
$httpBackend.flush();
expect(callback).not.toHaveBeenCalled();
}));
it('broadcasts "devise:unauthorized" on 401 error', inject(function ($rootScope) {
var callback = jasmine.createSpy('callback');
$rootScope.$on('devise:unauthorized', callback);
$http.get('/foo');
$httpBackend.flush();
expect(callback).toHaveBeenCalled();
}));
it('passes response to broadcast', inject(function ($rootScope) {
var response;
$rootScope.$on('devise:unauthorized', function(event, resp) {
response = resp;
});
$http.get('/foo');
$httpBackend.flush();
expect(response.status).toBe(401);
}));
it('passes a deferred to broadcast', inject(function ($rootScope) {
var deferred;
$rootScope.$on('devise:unauthorized', function(event, resp, d) {
deferred = d;
});
$http.get('/foo');
$httpBackend.flush();
expect(typeof deferred.resolve).toBe('function');
expect(typeof deferred.reject).toBe('function');
}));
it("returns deferred's promise", inject(function ($rootScope) {
var data = {};
$rootScope.$on('devise:unauthorized', function(event, response, deferred) {
deferred.resolve(data);
});
var ret;
$http.get('/foo').then(function(data) {
ret = data;
});
$httpBackend.flush();
expect(ret).toBe(data);
}));
});
describe('when interceptAuth is false (default)', function() {
it('returns rejected promise on 401', function () {
var callback = jasmine.createSpy('callback');
$http.get('/foo').catch(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalled();
});
it('can be enabled per request', inject(function ($rootScope) {
var callback = jasmine.createSpy('callback');
$rootScope.$on('devise:unauthorized', callback);
$http.get('/foo', { interceptAuth: true });
$httpBackend.flush();
expect(callback).toHaveBeenCalled();
}));
});
});
});
================================================
FILE: test/spec/auth.js
================================================
'use strict';
describe('Provider: Devise.Auth', function () {
var AuthProvider;
// load the service's module
beforeEach(module('Devise', function(_AuthProvider_) {
AuthProvider = _AuthProvider_;
}));
// instantiate service
var Auth, $rootScope, $http, $httpBackend;
beforeEach(inject(function (_Auth_, _$rootScope_, _$http_, _$httpBackend_) {
Auth = _Auth_;
$rootScope = _$rootScope_;
$http = _$http_;
$httpBackend = _$httpBackend_;
}));
function forceSignIn(Auth, user) {
user = (user === undefined) ? {} : user;
Auth._currentUser = user;
return user;
}
function jsonEquals(obj, other) {
return JSON.stringify(obj) === JSON.stringify(other);
}
function constantTrue() {
return true;
}
describe('can configure', function() {
function initService(fn) {
fn();
inject(function($q, $http, $rootScope) {
Auth = new AuthProvider.$get($q, $http, $rootScope);
});
}
function testPathConfigure(action, method, overrideMethod) {
initService(function() {
if (overrideMethod) {
AuthProvider[action + 'Method'](method);
}
AuthProvider[action + 'Path']('/test/test');
});
$httpBackend.expect(method, '/test/test').respond({});
Auth[action]();
$httpBackend.flush();
}
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('.loginPath', function() {
testPathConfigure('login', 'POST');
});
it('.logoutPath', function() {
testPathConfigure('logout', 'DELETE');
});
it('.registerPath', function() {
testPathConfigure('register', 'POST');
});
it('.sendResetPasswordInstructionsPath', function() {
testPathConfigure('sendResetPasswordInstructions', 'POST');
});
it('.resetPasswordPath', function() {
testPathConfigure('resetPassword', 'PUT');
});
it('.loginMethod', function() {
testPathConfigure('login', 'GET', true);
});
it('.logoutMethod', function() {
testPathConfigure('logout', 'GET', true);
});
it('.registerMethod', function() {
testPathConfigure('register', 'GET', true);
});
it('.sendResetPasswordInstructionsMethod', function() {
testPathConfigure('sendResetPasswordInstructions', 'GET', true);
});
it('.resetPasswordMethod', function() {
testPathConfigure('resetPassword', 'GET', true);
});
it('.parse', function() {
initService(function() {
AuthProvider.parse(function(response) {
return new User(response.data);
});
});
var User = function(params) {
this.params = params;
};
var params = {id: 1, name: 'test', email: 'test@email.com', password: 'password'};
var callCount = 0;
var callback = function(user) {
expect(user instanceof User).toBe(true);
expect(user.params).toEqual(params);
++callCount;
};
$httpBackend.expect('POST', '/users/sign_in.json').respond(params);
Auth.login().then(callback);
$httpBackend.flush();
expect(callCount).toBe(1);
});
it('.baseUrl', function() {
var baseUrl = 'http://localhost:3000';
initService(function() {
AuthProvider.baseUrl(baseUrl);
});
expect(AuthProvider.baseUrl()).toEqual(baseUrl);
});
describe('.resourceName', function() {
var credentials = {test: 'test'};
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('truthy resourceName', function() {
it('.login', function() {
initService(function() {
AuthProvider.resourceName('test');
});
$httpBackend.expect('POST', '/users/sign_in.json', {test: credentials}).respond({});
Auth.login({test: 'test'});
$httpBackend.flush();
});
it('.register', function() {
initService(function() {
AuthProvider.resourceName('test');
});
$httpBackend.expect('POST', '/users.json', {test: credentials}).respond({});
Auth.register({test: 'test'});
$httpBackend.flush();
});
});
describe('falsey resourceName', function() {
it('.login', function() {
initService(function() {
AuthProvider.resourceName(false);
});
$httpBackend.expect('POST', '/users/sign_in.json', credentials).respond({});
Auth.login({test: 'test'});
$httpBackend.flush();
});
it('.register', function() {
initService(function() {
AuthProvider.resourceName(false);
});
$httpBackend.expect('POST', '/users.json', credentials).respond({});
Auth.register({test: 'test'});
$httpBackend.flush();
});
});
});
});
describe('.login', function() {
var user;
var creds = {email: 'test', blah: true};
var postCallback, headerCallback;
function callbackWraper(data) {
data = JSON.parse(data);
return postCallback(data);
}
function headerWrapper(headers) {
return headerCallback(headers);
}
beforeEach(function() {
headerCallback = postCallback = constantTrue;
user = {id: 1, name: 'test', email: 'test@email.com', password: 'password'};
$httpBackend.expect('POST', '/users/sign_in.json', callbackWraper, headerWrapper).respond(user);
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('POSTs to /users/sign_in.json', function() {
Auth.login();
$httpBackend.flush();
});
it('POSTS credential data', function() {
postCallback = function(data) {
return jsonEquals(data.user, creds);
};
Auth.login(creds);
$httpBackend.flush();
});
it('returns a promise', function() {
expect(Auth.login().then).toBeDefined();
$httpBackend.flush();
});
it('resolves promise to currentUser', function() {
var callback = jasmine.createSpy('callback');
Auth.login().then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(user);
});
it('broadcasts the session event and login events', function() {
var loginCallback = jasmine.createSpy('login callback');
var sessionCallback = jasmine.createSpy('session callback');
$rootScope.$on('devise:new-session', sessionCallback);
$rootScope.$on('devise:login', loginCallback);
Auth.login(creds);
$httpBackend.flush();
expect(loginCallback).toHaveBeenCalledWith(jasmine.any(Object), user);
expect(sessionCallback).toHaveBeenCalledWith(jasmine.any(Object), user);
});
it('sends additional config to underlying $http', function() {
headerCallback = function(headers) {
return headers.test;
};
var headers = { test: true };
Auth.login(user, {headers: headers});
$httpBackend.flush();
});
});
describe('.logout', function() {
var headerCallback;
function headerWrapper(headers) {
return headerCallback(headers);
}
beforeEach(function() {
headerCallback = constantTrue;
$httpBackend.expect('DELETE', '/users/sign_out.json', null, headerWrapper).respond({});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('DELETEs to /users/sign_out.json', function() {
Auth.logout();
$httpBackend.flush();
});
it('returns a promise', function() {
expect(Auth.logout().then).toBeDefined();
$httpBackend.flush();
});
it('resolves promise to old currentUser', function() {
var user = forceSignIn(Auth, {id: 0});
var callback = jasmine.createSpy('callback');
Auth.logout().then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(user);
});
it('broadcasts the logout event', function() {
var callback = jasmine.createSpy('logout callback');
$rootScope.$on('devise:logout', callback);
Auth.logout();
$httpBackend.flush();
expect(callback).toHaveBeenCalled();
});
it('sends additional config to underlying $http', function() {
headerCallback = function(headers) {
return headers.test;
};
var headers = { test: true };
Auth.logout({headers: headers});
$httpBackend.flush();
});
});
describe('.register', function() {
var user;
var postCallback, headerCallback;
function callbackWraper(data) {
data = JSON.parse(data);
return postCallback(data);
}
function headerWrapper(headers) {
return headerCallback(headers);
}
beforeEach(function() {
headerCallback = postCallback = constantTrue;
user = {id: 1, name: 'test', email: 'test@email.com', password: 'password'};
$httpBackend.expect('POST', '/users.json', callbackWraper, headerWrapper).respond(user);
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('POSTs to /users.json', function() {
Auth.register();
$httpBackend.flush();
});
it('POSTS credential data', function() {
var u = {email: 'test', blah: true};
postCallback = function(data) {
return jsonEquals(data.user, u);
};
Auth.register(u);
$httpBackend.flush();
});
it('returns a promise', function() {
expect(Auth.register().then).toBeDefined();
$httpBackend.flush();
});
it('resolves promise to currentUser', function() {
var callback = jasmine.createSpy('callback');
Auth.register().then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(user);
});
it('broadcasts the new-registration event after a sucessful registration', function() {
var callback = jasmine.createSpy('callback');
$rootScope.$on('devise:new-registration', callback);
Auth.register();
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(jasmine.any(Object), user);
});
it('sends additional config to underlying $http', function() {
headerCallback = function(headers) {
return headers.test;
};
var headers = { test: true };
Auth.register(null, {headers: headers});
$httpBackend.flush();
});
});
describe('.sendResetPasswordInstructions', function() {
var user;
var postCallback;
function constantTrue() {
return true;
}
function callbackWraper(data) {
data = JSON.parse(data);
return postCallback(data);
}
beforeEach(function() {
postCallback = constantTrue;
$httpBackend.expect('POST', '/users/password.json', callbackWraper).respond("");
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('POSTs to /users/password.json', function() {
Auth.sendResetPasswordInstructions();
$httpBackend.flush();
});
it('POSTs email data', function() {
var u = {user: {email: 'new_email'}};
postCallback = function(data) {
return jsonEquals(data.user, u);
};
Auth.sendResetPasswordInstructions(u);
$httpBackend.flush();
});
it('returns a promise', function() {
expect(Auth.sendResetPasswordInstructions().then).toBeDefined();
$httpBackend.flush();
});
it('broadcasts the send-reset-password-instructions-successfully event after a sucessful sendResetPasswordInstructions', function() {
var callback = jasmine.createSpy('callback');
$rootScope.$on('devise:send-reset-password-instructions-successfully', callback);
Auth.sendResetPasswordInstructions();
$httpBackend.flush();
});
});
describe('.resetPassword', function() {
var user;
var postCallback;
function constantTrue() {
return true;
}
function callbackWraper(data) {
data = JSON.parse(data);
return postCallback(data);
}
beforeEach(function() {
postCallback = constantTrue;
user = {user: { id: 1, name: 'test', email: 'test@email.com', password: 'password'}};
$httpBackend.expect('PUT', '/users/password.json', callbackWraper).respond(user);
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('PUTs to /users/password.json', function() {
Auth.resetPassword();
$httpBackend.flush();
});
it('PUTs updated data', function() {
var u = {user: {password: 'new_password', password_confirmation: 'new_password', reset_password_token: 'token'}};
postCallback = function(data) {
return jsonEquals(data.user, u);
};
Auth.resetPassword(u);
$httpBackend.flush();
});
it('returns a promise', function() {
expect(Auth.resetPassword().then).toBeDefined();
$httpBackend.flush();
});
it('resolves promise to currentUser', function() {
var callback = jasmine.createSpy('callback');
Auth.resetPassword().then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(user);
});
it('broadcasts the reset-password-successfully event after a sucessful resetPassword', function() {
var callback = jasmine.createSpy('callback');
$rootScope.$on('devise:reset-password-successfully', callback);
Auth.resetPassword();
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(jasmine.any(Object), user);
});
});
describe('.parse', function() {
beforeEach(function() {
var response = {id: 1, name: 'test', email: 'test@email.com'};
$httpBackend.when('POST', '/users/sign_in.json').respond(response);
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('can be customized', function() {
var User = function(params) {
this.params = params;
};
var callCount = 0;
Auth.login().then(function(user){
expect(user instanceof User).toBe(false);
++callCount;
});
Auth.parse = function(response) {
return new User(response.data);
};
Auth.login().then(function(user){
expect(user instanceof User).toBe(true);
++callCount;
});
$httpBackend.flush();
expect(callCount).toBe(2);
});
});
describe('.currentUser', function() {
describe('when authenticated', function() {
var user;
beforeEach(function() {
user = forceSignIn(Auth);
});
it('returns a promise', function() {
var callback = jasmine.createSpy('callback');
Auth.currentUser().then(callback);
// use #$apply to have the promise resolve.
$rootScope.$apply();
expect(callback).toHaveBeenCalled();
});
it('resolves the promise with the currentUser', function() {
var callback = jasmine.createSpy('callback');
Auth.currentUser().then(callback);
// use #$apply to have the promise resolve.
$rootScope.$apply();
expect(callback).toHaveBeenCalledWith(user);
});
it('does not broadcasts any events', function() {
var callback = jasmine.createSpy('any callback');
$rootScope.$on('devise:new-session', callback);
$rootScope.$on('devise:login', callback);
Auth.currentUser();
$rootScope.$apply();
expect(callback).not.toHaveBeenCalled();
});
});
describe('when unauthenticated', function() {
describe('when user is logged in on server', function() {
var user = {};
beforeEach(function() {
$httpBackend.expect('POST', '/users/sign_in.json').respond(user);
});
it('fetches user from server', function() {
Auth.currentUser();
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('resolves promise with fetched user', function() {
var callback = jasmine.createSpy('callback');
Auth.currentUser().then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledWith(user);
});
it('broadcasts the session event but not the login event', function() {
var loginCallback = jasmine.createSpy('login callback');
var sessionCallback = jasmine.createSpy('new-session callback');
$rootScope.$on('devise:new-session', sessionCallback);
$rootScope.$on('devise:login', loginCallback);
Auth.currentUser();
$httpBackend.flush();
expect(sessionCallback).not.toHaveBeenCalled();
expect(loginCallback).toHaveBeenCalledWith(jasmine.any(Object), user);
});
});
describe('when user is not logged in on server', function() {
var error = {error: 'unauthorized'};
beforeEach(function() {
$httpBackend.expect('POST', '/users/sign_in.json').respond(401, error);
});
it('does not resolve promise', function() {
var callback = jasmine.createSpy('callback');
Auth.currentUser().then(callback);
$httpBackend.flush();
expect(callback).not.toHaveBeenCalled();
});
});
});
});
describe('.isAuthenticated', function() {
it('returns false if no currentUser', function() {
forceSignIn(Auth, null);
expect(Auth.isAuthenticated()).toBe(false);
});
it('returns true if a currentUser', function() {
forceSignIn(Auth);
expect(Auth.isAuthenticated()).toBe(true);
});
});
});
gitextract_l6p1sy_y/
├── Gruntfile.js
├── LICENSE.md
├── README.md
├── bower.json
├── karma.conf.js
├── lib/
│ ├── devise-min.js
│ └── devise.js
├── package.json
├── src/
│ ├── 401.js
│ ├── auth.js
│ └── build/
│ └── devise.js
└── test/
├── devise.js
└── spec/
├── 401.js
└── auth.js
SYMBOL INDEX (32 symbols across 4 files)
FILE: lib/devise-min.js
function b (line 10) | function b(b,c,d){var h={method:f[b].toLowerCase(),url:e[b]};return c&&(...
function c (line 10) | function c(b,c){a.forEach(b,function(a,d){this[d+c]=function(a){return v...
function d (line 10) | function d(a){return function(){return a}}
function f (line 10) | function f(a){return j._currentUser=a,a}
function g (line 10) | function g(){f(null),j._promise=null}
function i (line 10) | function i(a){return function(b){return e.$broadcast("devise:"+a,b),b}}
FILE: lib/devise.js
function httpConfig (line 89) | function httpConfig(action, data, additionalConfig) {
function configure (line 111) | function configure(obj, suffix) {
function constant (line 145) | function constant(arg) {
function save (line 154) | function save(user) {
function reset (line 159) | function reset() {
function broadcast (line 164) | function broadcast(name) {
FILE: src/auth.js
function httpConfig (line 55) | function httpConfig(action, data, additionalConfig) {
function configure (line 77) | function configure(obj, suffix) {
function constant (line 120) | function constant(arg) {
function save (line 129) | function save(user) {
function reset (line 134) | function reset() {
function broadcast (line 139) | function broadcast(name) {
FILE: test/spec/auth.js
function forceSignIn (line 19) | function forceSignIn(Auth, user) {
function jsonEquals (line 24) | function jsonEquals(obj, other) {
function constantTrue (line 27) | function constantTrue() {
function initService (line 32) | function initService(fn) {
function testPathConfigure (line 38) | function testPathConfigure(action, method, overrideMethod) {
function callbackWraper (line 184) | function callbackWraper(data) {
function headerWrapper (line 188) | function headerWrapper(headers) {
function headerWrapper (line 254) | function headerWrapper(headers) {
function callbackWraper (line 310) | function callbackWraper(data) {
function headerWrapper (line 314) | function headerWrapper(headers) {
function constantTrue (line 379) | function constantTrue() {
function callbackWraper (line 382) | function callbackWraper(data) {
function constantTrue (line 427) | function constantTrue() {
function callbackWraper (line 430) | function callbackWraper(data) {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
{
"path": "Gruntfile.js",
"chars": 2482,
"preview": "/*global module:false*/\nmodule.exports = function(grunt) {\n\n // Project configuration.\n grunt.initConfig({\n "
},
{
"path": "LICENSE.md",
"chars": 1096,
"preview": "Copyright (c) 2013 Justin Ridgewell\n===================================\n\nPermission is hereby granted, free of charge, t"
},
{
"path": "README.md",
"chars": 17506,
"preview": "AngularDevise [](http://travis-ci.org/cloudspace/ang"
},
{
"path": "bower.json",
"chars": 376,
"preview": "{\n \"name\": \"AngularDevise\",\n \"main\": \"lib/devise.js\",\n \"ignore\": [\n \"**/.*\",\n \"node_modules\",\n \"components\","
},
{
"path": "karma.conf.js",
"chars": 1073,
"preview": "module.exports = function(config) {\n config.set({\n // base path, that will be used to resolve files and exclud"
},
{
"path": "lib/devise-min.js",
"chars": 2671,
"preview": "// AngularDevise\n// -------------------\n// v1.2.1\n//\n// Copyright (c)2016 Justin Ridgewell\n// Distributed under MIT lice"
},
{
"path": "lib/devise.js",
"chars": 13845,
"preview": "(function(angular) {\n 'use strict';\n var devise = angular.module('Devise', []);\n\n devise.provider('AuthIntercep"
},
{
"path": "package.json",
"chars": 1129,
"preview": "{\n \"name\": \"AngularDevise\",\n \"version\": \"1.3.0\",\n \"description\": \"A small AngularJS Service to interact with De"
},
{
"path": "src/401.js",
"chars": 1206,
"preview": "devise.provider('AuthIntercept', function AuthInterceptProvider() {\n /**\n * Set to true to intercept 401 Unauthor"
},
{
"path": "src/auth.js",
"chars": 12714,
"preview": "devise.provider('Auth', function AuthProvider() {\n /**\n * The default paths.\n */\n var paths = {\n lo"
},
{
"path": "src/build/devise.js",
"chars": 153,
"preview": "(function(angular) {\n 'use strict';\n var devise = angular.module('Devise', []);\n\n // @include ../401.js\n // "
},
{
"path": "test/devise.js",
"chars": 43,
"preview": "var devise = angular.module('Devise', []);\n"
},
{
"path": "test/spec/401.js",
"chars": 3776,
"preview": "'use strict';\n\ndescribe('Service: Devise.401', function () {\n\n // load the service's module\n beforeEach(module('De"
},
{
"path": "test/spec/auth.js",
"chars": 21101,
"preview": "'use strict';\n\ndescribe('Provider: Devise.Auth', function () {\n\n var AuthProvider;\n // load the service's module\n "
}
]
About this extraction
This page contains the full source code of the cloudspace/angular_devise GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (77.3 KB), approximately 16.4k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.