diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index d856080..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,413 +0,0 @@ -// Generated on 2014-03-14 using generator-angular 0.7.1 -'use strict'; - -// # Globbing -// for performance reasons we're only matching one level down: -// 'test/spec/{,*/}*.js' -// use this if you want to recursively match all subfolders: -// 'test/spec/**/*.js' - -module.exports = function (grunt) { - - // Load grunt tasks automatically - require('load-grunt-tasks')(grunt); - - // Time how long tasks take. Can help when optimizing build times - require('time-grunt')(grunt); - - // configurable paths - var yeomanConfig = { - app: 'app', - dist: 'dist', - test: 'test' - }; - - try { - var component = require('./bower.json') - yeomanConfig.name = component.name || 'no-name'; - yeomanConfig.version = component.version || '0.0.0.undefined'; - } catch (e) {} - - - // Define the configuration for all the tasks - grunt.initConfig({ - - // Project settings - yeoman: yeomanConfig, - - // Watches files for changes and runs tasks based on the changed files - watch: { - js: { - files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], - tasks: [], - options: { - livereload: true - } - }, - jsTest: { - files: ['test/spec/{,*/}*.js'], - tasks: ['newer:jshint:test', 'karma'] - }, - styles: { - files: ['<%= yeoman.app %>/styles/{,*/}*.css'], - tasks: ['newer:copy:styles', 'autoprefixer'] - }, - gruntfile: { - files: ['Gruntfile.js'] - }, - livereload: { - options: { - livereload: '<%= connect.options.livereload %>' - }, - files: [ - '<%= yeoman.app %>/{,*/}*.html', - '.tmp/styles/{,*/}*.css', - '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' - ] - } - }, - - // The actual grunt server settings - connect: { - options: { - port: 9000, - // Change this to '0.0.0.0' to access the server from outside. - hostname: 'localhost', - livereload: 35729 - }, - livereload: { - options: { - open: true, - base: [ - '.tmp', - 'test', - '<%= yeoman.app %>' - ] - } - }, - test: { - options: { - port: 9001, - base: [ - '.tmp', - 'test', - '<%= yeoman.app %>' - ] - } - }, - dist: { - options: { - base: '<%= yeoman.dist %>' - } - } - }, - - // Make sure code styles are up to par and there are no obvious mistakes - jshint: { - options: { - jshintrc: '.jshintrc', - reporter: require('jshint-stylish') - }, - all: [ - 'Gruntfile.js', - '<%= yeoman.app %>/scripts/{,*/}*.js' - ], - test: { - options: { - jshintrc: 'test/.jshintrc' - }, - src: ['test/spec/{,*/}*.js'] - } - }, - - // Empties folders to start fresh - clean: { - dist: { - files: [{ - dot: true, - src: [ - '.tmp', - '<%= yeoman.dist %>/*', - '!<%= yeoman.dist %>/.git*' - ] - }] - }, - server: '.tmp' - }, - - // Add vendor prefixed styles - autoprefixer: { - options: { - browsers: ['last 1 version'] - }, - dist: { - files: [{ - expand: true, - cwd: '.tmp/styles/', - src: '{,*/}*.css', - dest: '.tmp/styles/' - }] - } - }, - - // Automatically inject Bower components into the app - 'bower-install': { - app: { - html: '<%= yeoman.app %>/index.html', - ignorePath: '<%= yeoman.app %>/' - } - }, - - // Renames files for browser caching purposes - rev: { - dist: { - files: { - src: [ - '<%= yeoman.dist %>/scripts/{,*/}*.js', - '<%= yeoman.dist %>/styles/{,*/}*.css', - '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', - '<%= yeoman.dist %>/styles/fonts/*' - ] - } - } - }, - - // Reads HTML for usemin blocks to enable smart builds that automatically - // concat, minify and revision files. Creates configurations in memory so - // additional tasks can operate on them - useminPrepare: { - html: '<%= yeoman.app %>/index.html', - options: { - dest: '<%= yeoman.dist %>' - } - }, - - // Performs rewrites based on rev and the useminPrepare configuration - usemin: { - html: ['<%= yeoman.dist %>/{,*/}*.html'], - css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], - options: { - assetsDirs: ['<%= yeoman.dist %>'] - } - }, - - // The following *-min tasks produce minified files in the dist folder - imagemin: { - dist: { - files: [{ - //expand: true, - //cwd: '<%= yeoman.app %>/images', - //src: '{,*/}*.{png,jpg,jpeg,gif}', - //dest: '<%= yeoman.dist %>/images' - }] - } - }, - svgmin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/images', - src: '{,*/}*.svg', - dest: '<%= yeoman.dist %>/images' - }] - } - }, - htmlmin: { - dist: { - options: { - collapseWhitespace: true, - collapseBooleanAttributes: true, - removeCommentsFromCDATA: true, - removeOptionalTags: true - }, - files: [{ - expand: true, - cwd: '<%= yeoman.dist %>', - src: ['*.html', 'views/{,*/}*.html'], - dest: '<%= yeoman.dist %>' - }] - } - }, - - // Allow the use of non-minsafe AngularJS files. Automatically makes it - // minsafe compatible so Uglify does not destroy the ng references - ngmin: { - dist: { - files: [{ - expand: true, - cwd: '.tmp/concat/scripts', - src: '*.js', - dest: '.tmp/concat/scripts' - }] - } - }, - - // Replace Google CDN references - cdnify: { - dist: { - html: ['<%= yeoman.dist %>/*.html'] - } - }, - - // Copies remaining files to places other tasks can use - copy: { - dist: { - files: [{ - expand: true, - dot: true, - cwd: '<%= yeoman.app %>', - dest: '<%= yeoman.dist %>', - src: [ - //'*.{ico,png,txt}', - //'.htaccess', - //'*.html', - 'views/{,*/}*.html', - //'bower_components/**/*', - //'images/{,*/}*.{webp}', - //'fonts/*' - ] - }, { - //expand: true, - //cwd: '.tmp/images', - //dest: '<%= yeoman.dist %>/images', - //src: ['generated/*'] - }] - }, - styles: { - //expand: true, - //cwd: '<%= yeoman.app %>/styles', - //dest: '.tmp/styles/', - //src: '{,*/}*.css' - } - }, - - // Run some tasks in parallel to speed up the build process - concurrent: { - server: [ - 'copy:styles' - ], - test: [ - 'copy:styles' - ], - dist: [ - 'copy:styles', - 'imagemin', - 'svgmin' - ] - }, - - // By default, your `index.html`'s will take care of - // minification. These next options are pre-configured if you do not wish - // to use the Usemin blocks. - // cssmin: { - // dist: { - // files: { - // '<%= yeoman.dist %>/styles/main.css': [ - // '.tmp/styles/{,*/}*.css', - // '<%= yeoman.app %>/styles/{,*/}*.css' - // ] - // } - // } - // }, - - //uglify: { - //options: { - //banner: '/* <%= yeoman.name %> - v<%= yeoman.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %> */\n\n' - //} - //}, - - uglify: { - options: { - banner: '/* <%= yeoman.name %> - v<%= yeoman.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %> */\n' - }, - dist: { - files: { - '<%= yeoman.dist %>/<%= yeoman.name %>.min.js': [ - '<%= yeoman.dist %>/<%= yeoman.name %>.js' - ] - } - } - }, - concat: { - options: { - banner: '/* <%= yeoman.name %> - v<%= yeoman.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %> */\n\n' - }, - dist: { - files: { - '<%= yeoman.dist %>/<%= yeoman.name %>.js': [ - '.tmp/scripts/{,*/}*.js', - '<%= yeoman.app %>/scripts/{,*/}*.js' - ] - } - } - }, - - // Test settings - karma: { - unit: { - configFile: 'karma.conf.js', - singleRun: false - } - }, - 'string-replace': { - dist: { - files: { - './': 'dist/**/*.*' - }, - options: { - replacements: [{ - pattern: /views\/templates\/default\.html/g, - replacement: 'bower_components/oauth-ng/dist/views/templates/default.html' - }] - } - } - } - - }); - - - grunt.registerTask('serve', function (target) { - if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); - } - - grunt.task.run([ - 'clean:server', - 'bower-install', - 'concurrent:server', - 'autoprefixer', - 'connect:livereload', - 'watch' - ]); - }); - - grunt.registerTask('server', function () { - grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); - grunt.task.run(['serve']); - }); - - grunt.registerTask('test', [ - 'clean:server', - 'concurrent:test', - 'autoprefixer', - 'connect:test', - 'karma' - ]); - - grunt.registerTask('build', [ - 'clean:dist', - 'copy', - 'useminPrepare', - 'concat', - 'ngmin', - 'uglify', - 'concat', - 'string-replace', - ]); - - grunt.registerTask('default', [ - 'newer:jshint', - 'test', - 'build' - ]); -}; diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e893381..0000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Andrea Reginato - -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. diff --git a/README.md b/README.md index a6a6813..f52905d 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,25 @@ -# Angular OAuth 2.0 Directive +# AngularJS directive for OAuth 2.0 -AngularJS directive for [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). - - -## Documentation +Source code for the AngularJS directive for OAuth 2.0 [website](http://andreareginato.github.com/oauth-ng). +[![oauth-ng](http://i.imgur.com/C0xCJcr.png)](https://andreareginato.github.com/oauth-ng) ## Contributing Fork the repo on github and send a pull requests with topic branches. -Do not forget to provide specs to your contribution. - -### Setup - -* Fork and clone the repository -* Run `npm install` - -### Unit tests (karma) - -* `grunt karma:unit` - -### Creating your own distribution -* Fork and clone the repository -* Run `npm install` -* Run `grunt build` - -The new distribution files will be created in the `dist/` folder. - -### Coding guidelines - -Follow [github](https://github.com/styleguide/) guidelines. ### Feedback Use the [issue tracker](http://github.com/andreareginato/oauth-ng/issues) for bugs. -[Mail](mailto:touch@lelylan.com) or [Tweet](http://twitter.com/lelylan) us for any idea -that can improve the project. +[Mail](mailto:andreareginato@gmail.com) or [Tweet](http://twitter.com/andreareginato) +us for any idea that can improve the project. -### Links +## Links * [GIT Repository](http://github.com/andreareginato/oauth-ng) -* [Lelylan Dev Center](http://dev.lelylan.com) - +* [The OAuth Bible](http://oauthbible.com/) +* [Lelylan](http://lelylan.com) ## Authors @@ -50,14 +27,9 @@ that can improve the project. ## Contributors -Special thanks to all [contributors](https://github.com/andreareginato/oauth-ng/contributors) +Special thanks to all [contributors](https://github.com/lelylan/lelylan-ng/contributors) for submitting patches. -## Changelog - -See [CHANGELOG](https://github.com/andreareginato/oauth-ng/blob/master/CHANGELOG.md) - -## Copyright +## License -Copyright (c) 2014 [Lelylan](http://lelylan.com). -See [LICENSE](https://github.com/andreareginato/oauth-ng/blob/master/LICENSE.md) for details. +See [license](https://github.com/lelylan/lelylan-ng/blob/master/LICENSE.md). diff --git a/app/.buildignore b/app/.buildignore deleted file mode 100644 index fc98b8e..0000000 --- a/app/.buildignore +++ /dev/null @@ -1 +0,0 @@ -*.coffee \ No newline at end of file diff --git a/app/.htaccess b/app/.htaccess deleted file mode 100644 index cb84cb9..0000000 --- a/app/.htaccess +++ /dev/null @@ -1,543 +0,0 @@ -# Apache Configuration File - -# (!) Using `.htaccess` files slows down Apache, therefore, if you have access -# to the main server config file (usually called `httpd.conf`), you should add -# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. - -# ############################################################################## -# # CROSS-ORIGIN RESOURCE SHARING (CORS) # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Cross-domain AJAX requests | -# ------------------------------------------------------------------------------ - -# Enable cross-origin AJAX requests. -# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity -# http://enable-cors.org/ - -# -# Header set Access-Control-Allow-Origin "*" -# - -# ------------------------------------------------------------------------------ -# | CORS-enabled images | -# ------------------------------------------------------------------------------ - -# Send the CORS header for images when browsers request it. -# https://developer.mozilla.org/en/CORS_Enabled_Image -# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html -# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ - - - - - SetEnvIf Origin ":" IS_CORS - Header set Access-Control-Allow-Origin "*" env=IS_CORS - - - - -# ------------------------------------------------------------------------------ -# | Web fonts access | -# ------------------------------------------------------------------------------ - -# Allow access from all domains for web fonts - - - - Header set Access-Control-Allow-Origin "*" - - - - -# ############################################################################## -# # ERRORS # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | 404 error prevention for non-existing redirected folders | -# ------------------------------------------------------------------------------ - -# Prevent Apache from returning a 404 error for a rewrite if a directory -# with the same name does not exist. -# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews -# http://www.webmasterworld.com/apache/3808792.htm - -Options -MultiViews - -# ------------------------------------------------------------------------------ -# | Custom error messages / pages | -# ------------------------------------------------------------------------------ - -# You can customize what Apache returns to the client in case of an error (see -# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: - -ErrorDocument 404 /404.html - - -# ############################################################################## -# # INTERNET EXPLORER # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Better website experience | -# ------------------------------------------------------------------------------ - -# Force IE to render pages in the highest available mode in the various -# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. - - - Header set X-UA-Compatible "IE=edge" - # `mod_headers` can't match based on the content-type, however, we only - # want to send this header for HTML pages and not for the other resources - - Header unset X-UA-Compatible - - - -# ------------------------------------------------------------------------------ -# | Cookie setting from iframes | -# ------------------------------------------------------------------------------ - -# Allow cookies to be set from iframes in IE. - -# -# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" -# - -# ------------------------------------------------------------------------------ -# | Screen flicker | -# ------------------------------------------------------------------------------ - -# Stop screen flicker in IE on CSS rollovers (this only works in -# combination with the `ExpiresByType` directives for images from below). - -# BrowserMatch "MSIE" brokenvary=1 -# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 -# BrowserMatch "Opera" !brokenvary -# SetEnvIf brokenvary 1 force-no-vary - - -# ############################################################################## -# # MIME TYPES AND ENCODING # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Proper MIME types for all files | -# ------------------------------------------------------------------------------ - - - - # Audio - AddType audio/mp4 m4a f4a f4b - AddType audio/ogg oga ogg - - # JavaScript - # Normalize to standard type (it's sniffed in IE anyways): - # http://tools.ietf.org/html/rfc4329#section-7.2 - AddType application/javascript js jsonp - AddType application/json json - - # Video - AddType video/mp4 mp4 m4v f4v f4p - AddType video/ogg ogv - AddType video/webm webm - AddType video/x-flv flv - - # Web fonts - AddType application/font-woff woff - AddType application/vnd.ms-fontobject eot - - # Browsers usually ignore the font MIME types and sniff the content, - # however, Chrome shows a warning if other MIME types are used for the - # following fonts. - AddType application/x-font-ttf ttc ttf - AddType font/opentype otf - - # Make SVGZ fonts work on iPad: - # https://twitter.com/FontSquirrel/status/14855840545 - AddType image/svg+xml svg svgz - AddEncoding gzip svgz - - # Other - AddType application/octet-stream safariextz - AddType application/x-chrome-extension crx - AddType application/x-opera-extension oex - AddType application/x-shockwave-flash swf - AddType application/x-web-app-manifest+json webapp - AddType application/x-xpinstall xpi - AddType application/xml atom rdf rss xml - AddType image/webp webp - AddType image/x-icon ico - AddType text/cache-manifest appcache manifest - AddType text/vtt vtt - AddType text/x-component htc - AddType text/x-vcard vcf - - - -# ------------------------------------------------------------------------------ -# | UTF-8 encoding | -# ------------------------------------------------------------------------------ - -# Use UTF-8 encoding for anything served as `text/html` or `text/plain`. -AddDefaultCharset utf-8 - -# Force UTF-8 for certain file formats. - - AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml - - - -# ############################################################################## -# # URL REWRITES # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Rewrite engine | -# ------------------------------------------------------------------------------ - -# Turning on the rewrite engine and enabling the `FollowSymLinks` option is -# necessary for the following directives to work. - -# If your web host doesn't allow the `FollowSymlinks` option, you may need to -# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the -# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks - -# Also, some cloud hosting services require `RewriteBase` to be set: -# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site - - - Options +FollowSymlinks - # Options +SymLinksIfOwnerMatch - RewriteEngine On - # RewriteBase / - - -# ------------------------------------------------------------------------------ -# | Suppressing / Forcing the "www." at the beginning of URLs | -# ------------------------------------------------------------------------------ - -# The same content should never be available under two different URLs especially -# not with and without "www." at the beginning. This can cause SEO problems -# (duplicate content), therefore, you should choose one of the alternatives and -# redirect the other one. - -# By default option 1 (no "www.") is activated: -# http://no-www.org/faq.php?q=class_b - -# If you'd prefer to use option 2, just comment out all the lines from option 1 -# and uncomment the ones from option 2. - -# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Option 1: rewrite www.example.com → example.com - - - RewriteCond %{HTTPS} !=on - RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Option 2: rewrite example.com → www.example.com - -# Be aware that the following might not be a good idea if you use "real" -# subdomains for certain parts of your website. - -# -# RewriteCond %{HTTPS} !=on -# RewriteCond %{HTTP_HOST} !^www\..+$ [NC] -# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] -# - - -# ############################################################################## -# # SECURITY # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Content Security Policy (CSP) | -# ------------------------------------------------------------------------------ - -# You can mitigate the risk of cross-site scripting and other content-injection -# attacks by setting a Content Security Policy which whitelists trusted sources -# of content for your site. - -# The example header below allows ONLY scripts that are loaded from the current -# site's origin (no inline scripts, no CDN, etc). This almost certainly won't -# work as-is for your site! - -# To get all the details you'll need to craft a reasonable policy for your site, -# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or -# see the specification: http://w3.org/TR/CSP). - -# -# Header set Content-Security-Policy "script-src 'self'; object-src 'self'" -# -# Header unset Content-Security-Policy -# -# - -# ------------------------------------------------------------------------------ -# | File access | -# ------------------------------------------------------------------------------ - -# Block access to directories without a default document. -# Usually you should leave this uncommented because you shouldn't allow anyone -# to surf through every directory on your server (which may includes rather -# private places like the CMS's directories). - - - Options -Indexes - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Block access to hidden files and directories. -# This includes directories used by version control systems such as Git and SVN. - - - RewriteCond %{SCRIPT_FILENAME} -d [OR] - RewriteCond %{SCRIPT_FILENAME} -f - RewriteRule "(^|/)\." - [F] - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Block access to backup and source files. -# These files may be left by some text editors and can pose a great security -# danger when anyone has access to them. - - - Order allow,deny - Deny from all - Satisfy All - - -# ------------------------------------------------------------------------------ -# | Secure Sockets Layer (SSL) | -# ------------------------------------------------------------------------------ - -# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: -# prevent `https://www.example.com` when your certificate only allows -# `https://secure.example.com`. - -# -# RewriteCond %{SERVER_PORT} !^443 -# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] -# - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Force client-side SSL redirection. - -# If a user types "example.com" in his browser, the above rule will redirect him -# to the secure version of the site. That still leaves a window of opportunity -# (the initial HTTP connection) for an attacker to downgrade or redirect the -# request. The following header ensures that browser will ONLY connect to your -# server via HTTPS, regardless of what the users type in the address bar. -# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ - -# -# Header set Strict-Transport-Security max-age=16070400; -# - -# ------------------------------------------------------------------------------ -# | Server software information | -# ------------------------------------------------------------------------------ - -# Avoid displaying the exact Apache version number, the description of the -# generic OS-type and the information about Apache's compiled-in modules. - -# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! - -# ServerTokens Prod - - -# ############################################################################## -# # WEB PERFORMANCE # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Compression | -# ------------------------------------------------------------------------------ - - - - # Force compression for mangled headers. - # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping - - - SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding - RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding - - - - # Compress all output labeled with one of the following MIME-types - # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` - # and can remove the `` and `` lines - # as `AddOutputFilterByType` is still in the core directives). - - AddOutputFilterByType DEFLATE application/atom+xml \ - application/javascript \ - application/json \ - application/rss+xml \ - application/vnd.ms-fontobject \ - application/x-font-ttf \ - application/x-web-app-manifest+json \ - application/xhtml+xml \ - application/xml \ - font/opentype \ - image/svg+xml \ - image/x-icon \ - text/css \ - text/html \ - text/plain \ - text/x-component \ - text/xml - - - - -# ------------------------------------------------------------------------------ -# | Content transformations | -# ------------------------------------------------------------------------------ - -# Prevent some of the mobile network providers from modifying the content of -# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. - -# -# Header set Cache-Control "no-transform" -# - -# ------------------------------------------------------------------------------ -# | ETag removal | -# ------------------------------------------------------------------------------ - -# Since we're sending far-future expires headers (see below), ETags can -# be removed: http://developer.yahoo.com/performance/rules.html#etags. - -# `FileETag None` is not enough for every server. - - Header unset ETag - - -FileETag None - -# ------------------------------------------------------------------------------ -# | Expires headers (for better cache control) | -# ------------------------------------------------------------------------------ - -# The following expires headers are set pretty far in the future. If you don't -# control versioning with filename-based cache busting, consider lowering the -# cache time for resources like CSS and JS to something like 1 week. - - - - ExpiresActive on - ExpiresDefault "access plus 1 month" - - # CSS - ExpiresByType text/css "access plus 1 year" - - # Data interchange - ExpiresByType application/json "access plus 0 seconds" - ExpiresByType application/xml "access plus 0 seconds" - ExpiresByType text/xml "access plus 0 seconds" - - # Favicon (cannot be renamed!) - ExpiresByType image/x-icon "access plus 1 week" - - # HTML components (HTCs) - ExpiresByType text/x-component "access plus 1 month" - - # HTML - ExpiresByType text/html "access plus 0 seconds" - - # JavaScript - ExpiresByType application/javascript "access plus 1 year" - - # Manifest files - ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" - ExpiresByType text/cache-manifest "access plus 0 seconds" - - # Media - ExpiresByType audio/ogg "access plus 1 month" - ExpiresByType image/gif "access plus 1 month" - ExpiresByType image/jpeg "access plus 1 month" - ExpiresByType image/png "access plus 1 month" - ExpiresByType video/mp4 "access plus 1 month" - ExpiresByType video/ogg "access plus 1 month" - ExpiresByType video/webm "access plus 1 month" - - # Web feeds - ExpiresByType application/atom+xml "access plus 1 hour" - ExpiresByType application/rss+xml "access plus 1 hour" - - # Web fonts - ExpiresByType application/font-woff "access plus 1 month" - ExpiresByType application/vnd.ms-fontobject "access plus 1 month" - ExpiresByType application/x-font-ttf "access plus 1 month" - ExpiresByType font/opentype "access plus 1 month" - ExpiresByType image/svg+xml "access plus 1 month" - - - -# ------------------------------------------------------------------------------ -# | Filename-based cache busting | -# ------------------------------------------------------------------------------ - -# If you're not using a build process to manage your filename version revving, -# you might want to consider enabling the following directives to route all -# requests such as `/css/style.12345.css` to `/css/style.css`. - -# To understand why this is important and a better idea than `*.css?v231`, read: -# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring - -# -# RewriteCond %{REQUEST_FILENAME} !-f -# RewriteCond %{REQUEST_FILENAME} !-d -# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] -# - -# ------------------------------------------------------------------------------ -# | File concatenation | -# ------------------------------------------------------------------------------ - -# Allow concatenation from within specific CSS and JS files, e.g.: -# Inside of `script.combined.js` you could have -# -# -# and they would be included into this single file. - -# -# -# Options +Includes -# AddOutputFilterByType INCLUDES application/javascript application/json -# SetOutputFilter INCLUDES -# -# -# Options +Includes -# AddOutputFilterByType INCLUDES text/css -# SetOutputFilter INCLUDES -# -# - -# ------------------------------------------------------------------------------ -# | Persistent connections | -# ------------------------------------------------------------------------------ - -# Allow multiple requests to be sent over the same TCP connection: -# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. - -# Enable if you serve a lot of static content but, be aware of the -# possible disadvantages! - -# -# Header set Connection Keep-Alive -# diff --git a/app/404.html b/app/404.html deleted file mode 100644 index fdace4a..0000000 --- a/app/404.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - Page Not Found :( - - - -
-

Not found :(

-

Sorry, but the page you were trying to view does not exist.

-

It looks like this was the result of either:

- - - -
- - diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index a7ad21c..0000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/images/configurations.png b/app/images/configurations.png deleted file mode 100644 index 4127ec7..0000000 Binary files a/app/images/configurations.png and /dev/null differ diff --git a/app/images/examples.png b/app/images/examples.png deleted file mode 100644 index 5a70f0d..0000000 Binary files a/app/images/examples.png and /dev/null differ diff --git a/app/images/glyphicons-halflings-white.png b/app/images/glyphicons-halflings-white.png deleted file mode 100644 index 3bf6484..0000000 Binary files a/app/images/glyphicons-halflings-white.png and /dev/null differ diff --git a/app/images/glyphicons-halflings.png b/app/images/glyphicons-halflings.png deleted file mode 100644 index ab5686e..0000000 Binary files a/app/images/glyphicons-halflings.png and /dev/null differ diff --git a/app/images/index-header.png b/app/images/index-header.png deleted file mode 100644 index 54474ca..0000000 Binary files a/app/images/index-header.png and /dev/null differ diff --git a/app/index.html b/app/index.html deleted file mode 100644 index 64ac058..0000000 --- a/app/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/robots.txt b/app/robots.txt deleted file mode 100644 index 9417495..0000000 --- a/app/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# robotstxt.org - -User-agent: * diff --git a/app/scripts/app.js b/app/scripts/app.js deleted file mode 100644 index 84ea7f6..0000000 --- a/app/scripts/app.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -// App libraries -var app = angular.module('oauth', [ - 'oauth.directive', // login directive - 'oauth.accessToken', // access token service - 'oauth.endpoint', // oauth endpoint service - 'oauth.profile', // profile model - 'oauth.interceptor' // bearer token interceptor -]) - -angular.module('oauth').config(['$locationProvider','$httpProvider', - function($locationProvider, $httpProvider) { - $locationProvider.html5Mode(true).hashPrefix('!'); // HTML5 mode - $httpProvider.interceptors.push('OAuthInterceptor'); // Authentication header - }]); diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js deleted file mode 100644 index 54b6f28..0000000 --- a/app/scripts/directives/oauth.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; - -var directives = angular.module('oauth.directive', []); - -directives.directive('oauth', ['AccessToken', 'Endpoint', 'Profile', '$location', '$rootScope', '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { - - var definition = { - restrict: 'AE', - replace: true, - scope: { - site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) - clientId: '@', // (required) client id - redirectUri: '@', // (required) client redirect uri - scope: '@', // (optional) scope - profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) - template: '@' // (optional) template to render (e.g views/templates/default.html) - } - }; - - definition.link = function postLink(scope, element, attrs) { - scope.show = 'none'; - - scope.$watch('client', function(value) { - init(); // sets defaults - compile(); // compiles the desired layout - Endpoint.set(scope); // sets the oauth authorization url - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - }); - - var init = function() { - scope.authorizePath = scope.authorizePath || '/oauth/authorize'; - scope.tokenPath = scope.tokenPath || '/oauth/token'; - scope.template = scope.template || 'views/templates/default.html'; - } - - var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); - $compile(element.contents())(scope); - }); - }; - - var initProfile = function(scope) { - var token = AccessToken.get(); - - if (token && token.access_token && scope.profileUri) { - Profile.get(scope.profileUri).success(function(response) { scope.profile = response }) - } - } - - var initView = function(token) { - var token = AccessToken.get(); - - if (!token) { return loggedOut() } // without access token it's logged out - if (token.access_token) { return loggedIn() } // if there is the access token we are done - if (token.error) { return denied() } // if the request has been denied we fire the denied event - } - - - scope.login = function() { - Endpoint.redirect(); - } - - scope.logout = function() { - AccessToken.destroy(scope); - loggedOut(); - } - - // set the oauth directive to the logged-in status - var loggedIn = function() { - $rootScope.$broadcast('oauth:success', AccessToken.get()); - scope.show = 'logged-in'; - } - - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); - scope.show = 'logged-out'; - } - - // set the oauth directive to the denied status - var denied = function() { - scope.show = 'denied'; - $rootScope.$broadcast('oauth:denied'); - } - - // Updates the template at runtime - scope.$on('oauth:template:update', function(event, template) { - scope.template = template; - compile(scope); - }); - }; - - return definition -}]); diff --git a/app/scripts/interceptors/oauth-interceptor.js b/app/scripts/interceptors/oauth-interceptor.js deleted file mode 100644 index 92485c0..0000000 --- a/app/scripts/interceptors/oauth-interceptor.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var service = angular.module('oauth.interceptor', []); - -service.factory('OAuthInterceptor', ['$rootScope', '$q', '$sessionStorage', - function ($rootScope, $q, $sessionStorage) { - - var service = {}; - - service.request = function(config) { - var token = $sessionStorage.token; - - if (token) - config.headers.Authorization = 'Bearer ' + token.access_token; - - if (token && expired(token)) - $rootScope.$broadcast('oauth:expired', token); - - return config; - }; - - var expired = function(token) { - return (token && token.expires_at && new Date(token.expires_at) < new Date()) - } - - return service; - }]); - diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js deleted file mode 100644 index e85bfbe..0000000 --- a/app/scripts/services/access-token.js +++ /dev/null @@ -1,151 +0,0 @@ -'use strict'; - -var service = angular.module('oauth.accessToken', ['ngStorage']); - -service.factory('AccessToken', ['$rootScope', '$location', '$http', '$sessionStorage', - function($rootScope, $location, $http, $sessionStorage) { - - var service = {}; - var token = null; - - - /* - * Returns the access token. - */ - - service.get = function() { - return token - } - - - /* - * Sets and returns the access token. It tries (in order) the following strategies: - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - - service.set = function() { - setTokenFromString(); - setTokenFromSession(); - return token - } - - - /* - * Delete the access token and remove the session. - */ - - service.destroy = function() { - delete $sessionStorage.token; - return token = null; - } - - - /* - * Tells if the access token is expired. - */ - - service.expired = function() { - return (token && token.expires_at && token.expires_at < new Date()) - } - - - - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ - - - /* - * Get the access token from a string and save it - */ - - var setTokenFromString = function() { - var token = getTokenFromString($location.hash()); - - if (token) { - removeFragment(); - setToken(token); - } - }; - - - /* - * Parse the fragment URI and return an object - */ - - var getTokenFromString = function(hash) { - var splitted = hash.split('&'); - var params = {}; - - for (var i = 0; i < splitted.length; i++) { - var param = splitted[i].split('='); - var key = param[0]; - var value = param[1]; - params[key] = value - } - - if (params.access_token || params.error) - return params; - } - - - /* - * Set the access token from the sessionStorage. - */ - - var setTokenFromSession = function() { - if ($sessionStorage.token) { - var params = $sessionStorage.token; - setToken(params); - } - } - - - /* - * Save the access token into the session - */ - - var setTokenInSession = function() { - $sessionStorage.token = token; - } - - - /* - * Set the access token. - */ - - var setToken = function(params) { - token = token || {} // init the token - angular.extend(token, params); // set the access token params - setExpiresAt(); // set the expiring time - setTokenInSession(); // save the token into the session - return token; - }; - - - /* - * Set the access token expiration date (useful for refresh logics) - */ - - var setExpiresAt = function() { - if (token) { - var expires_at = new Date(); - expires_at.setSeconds(expires_at.getSeconds() + parseInt(token.expires_in) - 60); // 60 seconds less to secure browser and response latency - token.expires_at = expires_at; - } - }; - - - /* - * Remove the fragment URI - * TODO we need to remove only the access token - */ - - var removeFragment = function(scope) { - $location.hash(''); - } - - - return service; -}]); diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js deleted file mode 100644 index 71e3c94..0000000 --- a/app/scripts/services/endpoint.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var client = angular.module('oauth.endpoint', []); - -client.factory('Endpoint', ['AccessToken', '$location', - function(AccessToken, $location) { - - var service = {}; - var params; - var url; - - - /* - * Defines the authorization URL - */ - - service.set = function(scope) { - url = scope.site + - scope.authorizePath + - '?response_type=token&' + - 'client_id=' + scope.clientId + '&' + - 'redirect_uri=' + scope.redirectUri + '&' + - 'scope=' + scope.scope + '&' + - 'state=' + $location.url() - - return url; - } - - - /* - * Returns the authorization URL - */ - - service.get = function() { - return url; - } - - - /* - * Redirects the app to the authorization URL - */ - - service.redirect = function() { - window.location.replace(url); - } - - return service; -}]); diff --git a/app/scripts/services/profile.js b/app/scripts/services/profile.js deleted file mode 100644 index e3f8062..0000000 --- a/app/scripts/services/profile.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -var client = angular.module('oauth.profile', []) - -client.factory('Profile', ['$http', function($http) { - var service = {} - - service.get = function(uri) { - return $http.get(uri); - } - - return service; -}]); diff --git a/app/styles/main.css b/app/styles/main.css deleted file mode 100644 index 5db42e1..0000000 --- a/app/styles/main.css +++ /dev/null @@ -1,798 +0,0 @@ -/* - * Lelylan Dev Center Documentation - * - * Table of contents: - * - saas - * - scaffolding - * - top (contextual) navigation - * - left navigation - * - code sintax - * - steps animations - */ - -$iconSpritePath: "../images/glyphicons-halflings.png"; -$iconWhiteSpritePath: "../images/glyphicons-halflings-white.png"; - -/* import to override variables and to access the mixins */ - -@import "http://github.com/bootstrap-sass/lib/bootstrap"; - -/* variables */ - -$lelylanColor: #BB234E; -$lelylanHoverColor: #018DCF; -$lelylanBackgroundColor: #009FC7; -$linkColor: $lelylanColor; -$linkColorHover: $lelylanHoverColor; - -/* mixins */ - -@mixin box-shadow { - $intensity: 0.2; - -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0,$intensity); - -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0,$intensity); - box-shadow: 0px 1px 3px rgba(0, 0, 0, $intensity); -} - - -/* * * * * * * */ -/* Scaffolding */ -/* * * * * * * */ - -/* body defaults */ - -body { - background-color: #f8f8f8 !important; - padding-top: 100px; /* Account for fixed navbar */ - position: relative; /* For scrollyspy */ -} - -/* anchor positioning */ - -h1[id] { - border-top:120px solid transparent; - margin-top:-120px; - -webkit-background-clip:padding-box; - -moz-background-clip:padding; - background-clip:padding-box; -} - -h2[id] { - padding-top: 100px; - margin-top: -90px; -} - -h3[id] { - border-top:100px solid transparent; - margin-top:-100px !important; - -webkit-background-clip:padding-box; - -moz-background-clip:padding; - background-clip:padding-box; -} - -/* titles */ - -.bs-title { - background-color: $lelylanBackgroundColor; - color: #fff; - margin: 0; - padding: 20px 60px; -} - -.bs-title h1 { - font-size: 48px; - font-weight: 200; -} - -.bs-title h4 { - color: #9EDAE6; - font-size: 22px; - font-weight: 100; - letter-spacing: 2px; - text-transform: uppercase; -} - -/* internal subtitles */ - -.bs-docs-section { - padding: 20px 60px 40px; - - h2 { - color: $lelylanColor; - font-size: 32px; - margin-bottom: 20px; - font-weight: 200; - } - - h3 { - font-size: 22px; - margin: 30px 0 20px; - font-weight: 200; - } - - p, - ul li { - font-size: 17px; - font-weight: 200; - line-height: 1.6; - } - - p { - margin-bottom: 10px; - } - - ul { - margin-bottom: 20px; - } -} - -/* multiselection subtitles */ - -.bs-title .nav-pills { - margin-top: 20px; - - li { - margin-right: 13px; - } - - li a { - background-color: #fff; - border: none; - border-radius: 0; - color: $lelylanColor; - } - - li.active a, - li a:hover { - background-color: $lelylanHoverColor; - color: #fff !important; - } -} - - -/* documentation page */ - -.bs-docs { - background-color: #fbfbfb; - padding: 0; - @include box-shadow; - - p a, - table a, - li a { - color: $linkColor !important; - } - - p a:hover, - table a:hover, - li a:hover { - text-decoration: none; - color: $linkColorHover !important; - } -} - -/* link colors animation */ - -a { - -webkit-transition: color 0.3s ease, text-decoration 0.3s ease, background 0.3s ease; - -moz-transition: color 0.3s ease, text-decoration 0.3s ease, background 0.3s ease; - transition: color 0.3s ease, text-decoration 0.3s ease, background 0.3s ease; -} - -/* all blocks following the first one */ - -.col-md-offset-3 { - margin-top: 50px; -} - - -/* * * * * * * * * */ -/* Apps navigation */ -/* * * * * * * * * */ - -/* lelylan apps navbar */ - -.lelylan-navbar-apps { - background-color: $lelylanBackgroundColor; - border: none; - height: 25px; - min-height: 25px; - margin-bottom: 0; - margin-top: 0; - - .container { - padding-left: 0; - padding-right: 0 - } - - .navbar-nav > li > a { - color: #40D4F0; - font-size: 11px; - padding: 2px 30px 0 0; - text-shadow: 0 0 0 #333; - text-transform: uppercase; - } - - .navbar-nav > li > a:hover, - .navbar-nav > li.active > a, - .navbar-nav > li.active > a:hover { - background-color: transparent; - color: #eee; - } - - .signout > a { - text-transform: none !important; - font-size: 12px !important; - padding-right: 0 !important; - font-weight: 200; - } -} - - -/* * * * * * * * * */ -/* Top navigation */ -/* * * * * * * * * */ - -/* Lelylan top menu navbar */ - -.lelylan-navbar-top-menu { - border: none; - height: 50px; - min-height: 50px; - margin-bottom: 0; - @include box-shadow; - - .container { - padding-right: 0; - } - - /* menu items */ - - .navbar-nav > li > a { - background-color: transparent; - color: $linkColor !important; - padding-right: 0; - padding-left: 20px; - font-weight: 200; - font-size: 15px; - } - - .navbar-nav > li > a:hover { - color: $linkColorHover !important; - } - - .navbar-nav > li > a.dropdown-toggle { - padding-left: 50px; - } - - .navbar-nav > li.active > a, - .navbar-nav > li.active:hover > a, - .navbar-nav > li.open > a, - .navbar-nav > li.open > a.dropdown-toggle { - background-color: transparent; - } - - .navbar-nav > li.active > a { - color: $lelylanHoverColor !important; - - } - - /* brand */ - - .navbar-brand { - font-size: 20px !important; - //text-shadow: 0 1px 0 #eee; - font-weight: 200; - padding-left: 15px !important; - color: #000 !important; - } -} - - -/* * * * * * * * * */ -/* Left navigation */ -/* * * * * * * * * */ - -/* By default it's not affixed in mobile views, so undo that */ - -.bs-sidebar.affix { - position: static; -} - -/* First level of nav */ - -.bs-sidenav { - //background-color: #f8f8f8; - margin-top: 30px; - margin-bottom: 30px; - padding-bottom: 10px; - //@include box-shadow; -} - -/* All levels of nav */ - -.bs-sidebar .nav > li > a { - color: $lelylanColor; - display: block; - font-size: 12px; - padding: 5px 10px; - text-transform: uppercase; -} - -.bs-sidebar .nav > li > a:hover, -.bs-sidebar .nav > li > a:focus { - text-decoration: none; - background-color: transparent; - color: $lelylanHoverColor; -} - -.bs-sidebar .nav > .active > a, -.bs-sidebar .nav > .active:hover > a, -.bs-sidebar .nav > .active:focus > a { - //font-weight: bold; - color: $lelylanHoverColor; - background-color: transparent; - border-left: 1px solid $lelylanHoverColor; -} - -/* Nav: second level (shown on .active) */ - -.bs-sidebar .nav .nav { - display: none; /* Hide by default, but at >768px, show it */ - margin-bottom: 8px; -} - -.bs-sidebar .nav .nav > li > a { - padding-top: 3px; - padding-bottom: 3px; - padding-left: 25px; - font-size: 12px; - line-height: 1.6em; - //text-transform: none; -} - -/* Show and affix the side nav when space allows it */ - -@media screen and (min-width: 992px) { - .bs-sidebar .nav > .active > ul { - display: block; - } - /* Widen the fixed sidebar */ - .bs-sidebar.affix, - .bs-sidebar.affix-bottom { - width: 213px; - } - .bs-sidebar.affix { - position: fixed; /* Undo the static from mobile first approach */ - top: 100px; - } - .bs-sidebar.affix-bottom { - position: absolute; /* Undo the static from mobile first approach */ - } - .bs-sidebar.affix-bottom .bs-sidenav, - .bs-sidebar.affix .bs-sidenav { - margin-top: 0; - margin-bottom: 0; - } -} - -/* Widen the fixed sidebar again */ - -@media screen and (min-width: 1200px) { - .bs-sidebar.affix-bottom, - .bs-sidebar.affix { - width: 263px; - } -} - - -/* * * * * * * */ -/* Code syntax */ -/* * * * * * * */ - -code { - color: #111; - background-color: transparent; - font-size: 75%; -} - -table code { - font-size: 90%; - padding-left: 0; -} - -pre code, -pre xmp { - display:block; - color:#111; - font-family: Monaco, Consolas, monospace; - font-size:12px; - line-height:1.5; - padding:5px; - margin: 5px 3px; - - hr { - margin: 15px 0 0; - } -} - -pre { - margin: 0 0 40px; - padding: 5px 0 5px 5px !important; - background: #f8f8f8; - border: 1px solid #ddd; - border-radius: 0; -} - -ul.nav-tabs, -ol.nav-tabs { - margin:15px 0 0 0; - line-height:1.7em -} - -ul.nav-tabs li.active a, -ol.nav-tabs li.active a { - background-color: #f8f8f8; -} - -ul.nav-tabs { - padding-left:0 -} - -div.tab-content pre { - border-top:none -} - -.nav-tabs > li > a { - font-size: 16px; - font-weight: 200; -} - -.nav-tabs > li a { - color: $linkColor; -} - -.nav-tabs > li.active a:hover, -.nav-tabs > li.active a { - color: $linkColorHover; - background-color: #f8f8f8; -} - -.code-block { - margin: 20px 0 30px; -} - - -/* * * * * * * * * */ -/* Animated steps */ -/* * * * * * * * * */ - -.animation { - max-width: 100%; - background-color: #fff; - padding: 0 15px 5px 20px; - border: 1px solid #ddd; - margin: 20px 0; - - h3 { - display:inline; - } - - p { - float:clear; - margin-top: 15px; - margin-left: 6px; - } - - pre { - margin-left: 4px; - } - - .button-lelylan { - background-color: $lelylanColor; - } - - .button-lelylan:hover { - color: #fff; - } - - .canvas { - position:relative; - width: 690px; - height: 40px; - margin: 20px 0 20px 0; - } - - .action { - padding-left: 5px; - padding-bottom: 10px; - } -} - - -/* * * * * * * * */ -/* Button style */ -/* * * * * * * * */ - -.btn-lelylan { - background: $lelylanColor; - box-sizing: border-box; - min-height: 35px; - width: auto; - display: inline-block; - padding: 0.9em 1.37em; - cursor: pointer; - text-decoration: none; - color: #fff; - font-size: 12px; - line-height: 13px; - font-weight: 300; - text-align: center; - letter-spacing: 1px; - text-transform: uppercase; - text-shadow: none; - border-radius: 0.2em; - border: rgba(0,0,0,0.05) 0.1em solid; - -webkit-transition: background 0.3s ease, border-color 0.3s ease; - -moz-transition: background 0.3s ease, border-color 0.3s ease; - transition: background 0.3s ease, border-color 0.3s ease; -} - -.btn-lelylan:hover { - background: $lelylanHoverColor; - color: #fff; - text-decoration: none; -} - -.button-group { - width: 390px; - margin:0 auto; - margin-bottom: 40px; - - a { - font-size: 16px - } -} - - -/* * * * * * * * */ -/* Table Listing */ -/* * * * * * * * */ - -table { - margin: 20px 0; - - tr th { - border-bottom: 1px solid #dddddd !important; - text-transform: uppercase; - font-style: italic; - } - - tr td, - tr th { - color: #333; - font-size: 15px; - font-weight: 200; - line-height: 1.5 !important; - } - - .parameter { - width: 30%; - } - - .extra { - width: 30% !important; - } - - .nested { - padding-left: 30px !important; - } - - .parameter span { - font-style: italic; - } - - span.info { - display: block; - color: #888; - font-style: italic; - } -} - -.table-labeled { - - .label { - padding: 0; - } - - .label { - border-radius: 2px; - font-size: 12px; - font-weight: 400; - line-height: 23px; - padding: 3px 6px 2px; - text-transform: uppercase; - background: #eee; - border: 1px solid #ddd; - color: #333; - } - - .link { - background: #E5F4FF; - border: 1px solid #C5EAFF; - } -} - - - - -/* * * * * * */ -/* Home Page */ -/* * * * * * */ - -body.presentation { - padding-top: 40px; - background-color: #fbfbfb !important; - text-rendering: optimizeLegibility; - - .lelylan-navbar-top-menu { - background-color: #fbfbfb !important; - } - - .presentation-block > .row { - padding: 90px 0 60px; - border-bottom: 1px solid #eee; - } - - .banner { - margin-top: 10px; - height: 360px; - //background: $lelylanColor url(http://github.com/images/index-header.png) center center; - background-color: #333; - color: #fff; - background-size: cover; - width: 100%; - position: relative; - border-bottom: 1px solid #ddd; - - .banner-content { - padding: 120px 40px; - font-weight: 300; - } - - p, h1 { - text-align: center; - background: #000 !important; - margin-bottom: 5px; - margin-left: 25px; - padding: 9px 15px 10px 20px; - color: #FFF; - } - - h1 { - font-size: 68px; - } - - p { - font-size: 32px; - font-weight: 100; - } - - .banner-button { - text-align: center; - - a { - border: #ddd 0.1em solid; - margin-top: 45px; - height: 52px; - font-size: 20px; - font-weight: 100; - letter-spacing: 2px; - } - } - - } - - h1 { - font-size: 2.8em; - border: none; - padding: 0; - margin: 0; - margin-bottom: 35px; - font-weight: 200; - } - - h4 { - color: #666; - font-size: 16px; - line-height: 26px; - text-transform: uppercase; - font-weight: 200; - margin-bottom: 0; - letter-spacing: 1px; - } - - p { - font-size: 18px; - font-weight: 200; - color: #333; - margin-bottom: 35px; - } - - .pull-left, - .pull-right { - padding-top: 20px; - } - - p a { - color: $linkColor; - } - - p a:hover { - text-decoration: none; - color: $linkColorHover; - } -} - -/* detailed blocks */ - -.bs-detailed h1 { - font-size: 64px !important; - font-weight: 200; -} - -.bs-detailed h2 { - margin-top: 40px; - margin-bottom: 25px; -} - - - -/* * * * * */ -/* Footer */ -/* * * * * */ - -footer .bs-docs-section { - padding-right: 0; - - a { - color: $linkColor !important; - } - a:hover { - text-decoration: none; - color: $linkColorHover !important; - } -} - - - -/* * * * * * * * */ -/* Miscellaneous */ -/* * * * * * * * */ - -.alert { - padding: 10px; - - .label-lelylan { - color: $lelylanColor; - text-transform: uppercase; - background-color: #fff; - margin-right: 5px; - font-weight: 400; - padding: .3em .6em .2em; - letter-spacing: 1px; - } - - p { - color: #3A3A3A; - margin-bottom: 0; - font-size: 16px; - } -} - -img { - margin-top: 10px; - margin-bottom: 30px; - border-radius: 50%; - border: 1px solid $lelylanColor; -} diff --git a/app/views/templates/button.html b/app/views/templates/button.html deleted file mode 100644 index ce7861a..0000000 --- a/app/views/templates/button.html +++ /dev/null @@ -1,5 +0,0 @@ - - Login Button - Logout {{profile.email}} - Access denied. - diff --git a/app/views/templates/default.html b/app/views/templates/default.html deleted file mode 100644 index 3591753..0000000 --- a/app/views/templates/default.html +++ /dev/null @@ -1,5 +0,0 @@ - - Sign In - Logout {{profile.full_name}} - Access denied. Try again. - diff --git a/bower.json b/bower.json deleted file mode 100644 index 0de5fcb..0000000 --- a/bower.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "oauth-ng", - "version": "0.1.1-beta", - "main": [ - "dist/oauth-ng.js", - "dist/views/templates/default" - ], - "ignore": [ - "app", - "test", - "bower.json", - "Gruntfile.js", - "karma.conf.js", - "package.json" - ], - "dependencies": { - "angular": "~1.2.16", - "ngstorage": "~0.3.0" - }, - "devDependencies": { - "json3": "~3.2.4", - "es5-shim": "~2.0.8", - "timecop": "~0.1.1", - "bootstrap-sass": "~3.0.2", - "jquery": "~1.9.1", - "angular-mocks": "~1.2.16" - } -} diff --git a/dist/ng-oauth.js b/dist/ng-oauth.js deleted file mode 100644 index 79bda58..0000000 --- a/dist/ng-oauth.js +++ /dev/null @@ -1,361 +0,0 @@ -/* ng-oauth - v0.1.1 - 2014-05-20 */ - -'use strict'; - -// App libraries -var app = angular.module('oauth', [ - 'oauth.directive', // login directive - 'oauth.accessToken', // access token service - 'oauth.endpoint', // oauth endpoint service - 'oauth.profile', // profile model - 'oauth.interceptor' // bearer token interceptor -]) - -angular.module('oauth').config(['$locationProvider','$httpProvider', - function($locationProvider, $httpProvider) { - $locationProvider.html5Mode(true).hashPrefix('!'); // HTML5 mode - $httpProvider.interceptors.push('OAuthInterceptor'); // Authentication header - }]); - -'use strict'; - -var directives = angular.module('oauth.directive', []); - -directives.directive('oauth', ['AccessToken', 'Endpoint', 'Profile', '$location', '$rootScope', '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { - - var definition = { - restrict: 'AE', - replace: true, - scope: { - site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) - clientId: '@', // (required) client id - redirectUri: '@', // (required) client redirect uri - scope: '@', // (optional) scope - profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) - template: '@' // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) - } - }; - - definition.link = function postLink(scope, element, attrs) { - scope.show = 'none'; - - scope.$watch('client', function(value) { - init(); // sets defaults - compile(); // compiles the desired layout - Endpoint.set(scope); // sets the oauth authorization url - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - }); - - var init = function() { - scope.authorizePath = scope.authorizePath || '/oauth/authorize'; - scope.tokenPath = scope.tokenPath || '/oauth/token'; - scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; - } - - var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); - $compile(element.contents())(scope); - }); - }; - - var initProfile = function(scope) { - var token = AccessToken.get(); - - if (token && token.access_token && scope.profileUri) { - Profile.get(scope.profileUri).success(function(response) { scope.profile = response }) - } - } - - var initView = function(token) { - var token = AccessToken.get(); - - if (!token) { return loggedOut() } // without access token it's logged out - if (token.access_token) { return loggedIn() } // if there is the access token we are done - if (token.error) { return denied() } // if the request has been denied we fire the denied event - } - - - scope.login = function() { - Endpoint.redirect(); - } - - scope.logout = function() { - AccessToken.destroy(scope); - loggedOut(); - } - - // set the oauth directive to the logged-in status - var loggedIn = function() { - $rootScope.$broadcast('oauth:success', AccessToken.get()); - scope.show = 'logged-in'; - } - - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); - scope.show = 'logged-out'; - } - - // set the oauth directive to the denied status - var denied = function() { - scope.show = 'denied'; - $rootScope.$broadcast('oauth:denied'); - } - - // Updates the template at runtime - scope.$on('oauth:template:update', function(event, template) { - scope.template = template; - compile(scope); - }); - }; - - return definition -}]); - -'use strict'; - -var service = angular.module('oauth.interceptor', []); - -service.factory('OAuthInterceptor', ['$rootScope', '$q', '$sessionStorage', - function ($rootScope, $q, $sessionStorage) { - - var service = {}; - - service.request = function(config) { - var token = $sessionStorage.token; - - if (token) - config.headers.Authorization = 'Bearer ' + token.access_token; - - if (token && expired(token)) - $rootScope.$broadcast('oauth:expired', token); - - return config; - }; - - var expired = function(token) { - return (token && token.expires_at && new Date(token.expires_at) < new Date()) - } - - return service; - }]); - - -'use strict'; - -var service = angular.module('oauth.accessToken', ['ngStorage']); - -service.factory('AccessToken', ['$rootScope', '$location', '$http', '$sessionStorage', - function($rootScope, $location, $http, $sessionStorage) { - - var service = {}; - var token = null; - - - /* - * Returns the access token. - */ - - service.get = function() { - return token - } - - - /* - * Sets and returns the access token. It tries (in order) the following strategies: - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - - service.set = function() { - setTokenFromString(); - setTokenFromSession(); - return token - } - - - /* - * Delete the access token and remove the session. - */ - - service.destroy = function() { - delete $sessionStorage.token; - return token = null; - } - - - /* - * Tells if the access token is expired. - */ - - service.expired = function() { - return (token && token.expires_at && token.expires_at < new Date()) - } - - - - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ - - - /* - * Get the access token from a string and save it - */ - - var setTokenFromString = function() { - var token = getTokenFromString($location.hash()); - - if (token) { - removeFragment(); - setToken(token); - } - }; - - - /* - * Parse the fragment URI and return an object - */ - - var getTokenFromString = function(hash) { - var splitted = hash.split('&'); - var params = {}; - - for (var i = 0; i < splitted.length; i++) { - var param = splitted[i].split('='); - var key = param[0]; - var value = param[1]; - params[key] = value - } - - if (params.access_token || params.error) - return params; - } - - - /* - * Set the access token from the sessionStorage. - */ - - var setTokenFromSession = function() { - if ($sessionStorage.token) { - var params = $sessionStorage.token; - setToken(params); - } - } - - - /* - * Save the access token into the session - */ - - var setTokenInSession = function() { - $sessionStorage.token = token; - } - - - /* - * Set the access token. - */ - - var setToken = function(params) { - token = token || {} // init the token - angular.extend(token, params); // set the access token params - setExpiresAt(); // set the expiring time - setTokenInSession(); // save the token into the session - return token; - }; - - - /* - * Set the access token expiration date (useful for refresh logics) - */ - - var setExpiresAt = function() { - if (token) { - var expires_at = new Date(); - expires_at.setSeconds(expires_at.getSeconds() + parseInt(token.expires_in) - 60); // 60 seconds less to secure browser and response latency - token.expires_at = expires_at; - } - }; - - - /* - * Remove the fragment URI - * TODO we need to remove only the access token - */ - - var removeFragment = function(scope) { - $location.hash(''); - } - - - return service; -}]); - -'use strict'; - -var client = angular.module('oauth.endpoint', []); - -client.factory('Endpoint', ['AccessToken', '$location', - function(AccessToken, $location) { - - var service = {}; - var params; - var url; - - - /* - * Defines the authorization URL - */ - - service.set = function(scope) { - url = scope.site + - scope.authorizePath + - '?response_type=token&' + - 'client_id=' + scope.clientId + '&' + - 'redirect_uri=' + scope.redirectUri + '&' + - 'scope=' + scope.scope + '&' + - 'state=' + $location.url() - - return url; - } - - - /* - * Returns the authorization URL - */ - - service.get = function() { - return url; - } - - - /* - * Redirects the app to the authorization URL - */ - - service.redirect = function() { - window.location.replace(url); - } - - return service; -}]); - -'use strict'; - -var client = angular.module('oauth.profile', []) - -client.factory('Profile', ['$http', function($http) { - var service = {} - - service.get = function(uri) { - return $http.get(uri); - } - - return service; -}]); diff --git a/dist/ng-oauth.min.js b/dist/ng-oauth.min.js deleted file mode 100644 index c7e596e..0000000 --- a/dist/ng-oauth.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* ng-oauth - v0.1.1 - 2014-05-20 */ -"use strict";var app=angular.module("oauth",["oauth.directive","oauth.accessToken","oauth.endpoint","oauth.profile","oauth.interceptor"]);angular.module("oauth").config(["$locationProvider","$httpProvider",function(a,b){a.html5Mode(!0).hashPrefix("!"),b.interceptors.push("OAuthInterceptor")}]);var directives=angular.module("oauth.directive",[]);directives.directive("oauth",["AccessToken","Endpoint","Profile","$location","$rootScope","$compile","$http","$templateCache",function(a,b,c,d,e,f,g,h){var i={restrict:"AE",replace:!0,scope:{site:"@",clientId:"@",redirectUri:"@",scope:"@",profileUri:"@",template:"@"}};return i.link=function(d,i){d.show="none",d.$watch("client",function(){j(),k(),b.set(d),a.set(d),l(d),m()});var j=function(){d.authorizePath=d.authorizePath||"/oauth/authorize",d.tokenPath=d.tokenPath||"/oauth/token",d.template=d.template||"bower_components/oauth-ng/dist/views/templates/default.html"},k=function(){g.get(d.template,{cache:h}).success(function(a){i.html(a),f(i.contents())(d)})},l=function(b){var d=a.get();d&&d.access_token&&b.profileUri&&c.get(b.profileUri).success(function(a){b.profile=a})},m=function(b){var b=a.get();return b?b.access_token?n():b.error?p():void 0:o()};d.login=function(){b.redirect()},d.logout=function(){a.destroy(d),o()};var n=function(){e.$broadcast("oauth:success",a.get()),d.show="logged-in"},o=function(){e.$broadcast("oauth:logout"),d.show="logged-out"},p=function(){d.show="denied",e.$broadcast("oauth:denied")};d.$on("oauth:template:update",function(a,b){d.template=b,k(d)})},i}]);var service=angular.module("oauth.interceptor",[]);service.factory("OAuthInterceptor",["$rootScope","$q","$sessionStorage",function(a,b,c){var d={};d.request=function(b){var d=c.token;return d&&(b.headers.Authorization="Bearer "+d.access_token),d&&e(d)&&a.$broadcast("oauth:expired",d),b};var e=function(a){return a&&a.expires_at&&new Date(a.expires_at) - Login Button - Logout {{profile.email}} - Access denied. - diff --git a/dist/views/templates/default.html b/dist/views/templates/default.html deleted file mode 100644 index 3591753..0000000 --- a/dist/views/templates/default.html +++ /dev/null @@ -1,5 +0,0 @@ - - Sign In - Logout {{profile.full_name}} - Access denied. Try again. - diff --git a/index.html b/index.html new file mode 100644 index 0000000..4b764e8 --- /dev/null +++ b/index.html @@ -0,0 +1,981 @@ + + + + oauth-ng | AngularJS directive for OAuth 2.0 + + + + + + + + +
+ +
+ +
AngularJS directive for OAuth 2.0
+

+ See on Github +

+
+
+ +
+
+

Introduction

+

+ Create AngularJS apps that connects to any OAuth 2.0 server using the client side + flow (aka OAuth 2.0 Implicit Grant). + In addition, OpenID Connect Implicit Flow + is also supported. +

+
+
+ + +
+
+

Getting Started

+ +

Installation

+ +

+ Install oauth-ng using Bower +

+ +
$ bower install oauth-ng --save
+ +

Basic Example

+ +

+ This example shows you how the directive works. +

+ +
<body ng-app="app">
+  <oauth
+    site="OAUTH_SERVER_URI_HERE"
+    client-id="CLIENT_ID_HERE"
+    redirect-uri="REDIRECT_URI_HERE"
+    profile-uri="PROFILE_URI_HERE"
+    scope="SCOPE_HERE">
+  </oauth>
+
+  <script>angular.module('app', ['oauth']);</script>
+</body>
+ +

+ To fully understand how it works read the next section. +

+ + +

What does it do?

+ +

+ It renders a Login link that lets you authorize a third party app through OAuth 2.0. + When the flow is completed, the access token is saved in the SessionStorage. +

+ +
+
+ + +
+
+ + +

Create your first app with oauth-ng

+

+ In this section you will learn how to use the directive to authorize an OAuth 2.0 server + through the creation of a real project. It will take less than 10 minutues. +

+ +

+ View Demo + Download Source +

+ + +

Setup

+ +

+ To build our app we'll use Yeoman, a collection of tools + and frameworks helping developers to quickly build web applications. +

+ +
    +
  • + yo - perform ripetitive tasks. +
  • +
  • + grunt - build, preview and test your project. +
  • +
  • + bower - solve the frontend package management. +
  • +
+ + +

Installation

+ +

+ With a recent version of Node.js installed, install the yo package. + In this way you have Yo, Grunt and Bower and can run them directly from the command-line. +

+ +
$ npm install -g yo
+ +

+ With Yeoman you can install additional generators with npm. For this tutorial you need + to install the AngularJS generator. +

+ +
$ npm install -g generator-angular
+ + +

Create your AngularJS app

+ +

+ To begin, go to the terminal, make a new directory and cd into it. +

+ +
$ mkdir new-project && cd $_
+ +

+ You can now kick-start your AngularJS app. +

+ +
$ yo angular
+ +

+ It will also ask you if you would like to include Twitter Bootstrap and other stuff. + Once you've decided, just hit Enter. It will take a while to complete. +

+ +

+ To preview what the app looks like run the serve command. +

+ +
$ grunt serve
+ +

+ The server supports LiveReload, meaning you can fire up a text editor, edit a custom + element and the browser will reload on save. +

+ + +

Install oauth-ng

+ +

+ Install oauth-ng using Bower. +

+ +
$ bower install oauth-ng --save
+ +

+ Now you have oauth-ng and all its dependencies ready to be used. + Restart the server to automatically add them to your index page. +

+ +
$ grunt serve
+ +

+ The setup is now completed. +

+ + +

AngularJS app definition

+ +

+ Inject the oauth-ng module into your application. +

+ +
// app/scripts/app.js
+angular.module('newProjectApp', ['oauth', ... ])
+ +

Activate the HTML5 mode

+ +

+ Activate the HTML5 mode to catch the access token. +

+ +
// app/scripts/app.js
+angular.module('newProjectApp').config(function($locationProvider) {
+  $locationProvider.html5Mode(true).hashPrefix('!');
+});
+ +

OAuth 2.0 Server

+ +

+ To test the directive we created a basic OAuth 2.0 authorization server at the address + http://oauth-ng-server.herokuapp.com. +

+ + +

Identify your application

+ +

+ To make the directive work you need a registered application to get its + client-id and redirect-uri. For this example we + created a demo app with the following credentials. +

+ +
    +
  • client-id - 017b9f702a904869a6e52bd39b147bb912
  • +
  • redirect-uri - http://localhost:9000
  • +
+ + +

Add the oauth-ng directive

+ +

+ Open your main.html view and place the oauth tag with the needed configurations. +

+ +
<oauth
+  site="http://oauth-ng-server.herokuapp.com/"
+  client-id="d6d2b510d18471d2e22aa202216e86c42beac80f9a6ac2da505dcb79c7b2fd99"
+  redirect-uri="http://localhost:9000/"
+  profile-uri="http://oauth-ng-server.herokuapp.com/api/v1/me"
+  scope="public">
+</oauth>
+ +

+ Here a description for what these settings are about. +

+ + + + + + + + + + + + + + + + + + + + + + + + +
+ site + + OAuth 2.0 authorization server URI. +
+ client-id + + Registered Client ID. +
+ redirect-uri + + Registered application URI.
Where the user is redirected after the authorization. +
+ profile-uri + + API endpoint to get the authenticated profile resource. +
+ scope + + Application privileges to be requested from the authorization server. The value should be in plain text and will be properly URI encoded for authorization. +
+ + +

You're done!

+ +

+ Open your index page, click to the Login link and authorize your application + to get a new access token. You are now ready to access your API using OAuth + 2.0 and AngularJS. +

+ +

+ View Demo + Download Source +

+ + + + + +
+
+ + + +
+
+ +

HTML5 mode

+ + +

HTML5 mode ON

+ +

The oauth directive works just straight when the HTML5 mode is active.

+ +
<script>
+  angular.module('app').config(function($locationProvider) {
+    $locationProvider.html5Mode(true).hashPrefix('!');
+  });</script>
+ + +

HTML5 mode OFF

+ +

+ When the HTML5 mode is off (default setup on AngularJS) add the following + snippet of code to your routing provider. +

+ +
angular.module('app').config(function ($routeProvider) {
+  $routeProvider
+    .when('/access_token=:accessToken', {
+      template: '',
+      controller: function ($location, AccessToken) {
+        var hash = $location.path().substr(1);
+        AccessToken.setTokenFromString(hash);
+        $location.path('/');
+        $location.replace();
+      }
+    })
+    ...
+ + +

+ This code is needed because the fragment that the OAuth 2.0 server returns is recognized + as the routing path /access_token. For this reason you need to add a routing + rule catching the access token, parse it and then redirect to the desired view of your app. +

+ +
+
+ + +
+
+

Configurations

+ +

+ The directive accepts the following attributes. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ site + required + + A string that represents the authorization endpoint. +
+ client-id + required + + Registered Client ID. +
+ response-type + required + + Default to token, which is for OAuth 2.0 implicit grant type. To use OpenID Connect Implicit Flow, + use id_token (to get id_token only) or id_token token (to get both id_token and access_token) +
+ redirect-uri + required + + Registered application URI where the user is redirected after the authorization. +
+ scope + optional + + Application privileges to be requested from the authorization server. The value should be in plain text and will be properly URI encoded for authorization. + When using OpenID Connect Implicit Flow, value openid must be included. +
+ profile-uri + optional + + API endpoint returning the authenticated profile resource. +
+ state + optional + + An arbitrary unique string created by your app to guard against Cross-site Request Forgery. +
+ template + optional + + Custom template you want to render.
+ Default to default.html. +
+ authorize-path + optional + + Authorization URL for the OAuth2 Implicit Flow.
+ Default to /oauth/authorize. +
+ text + optional + + Visible text when the user has to login.
+ Default to Sign in. +
+ storage + (optional) + + How the token is stored locally,
+ localStorage or sessionStorage.
+ Defaults to sessionStorage. +
+ issuer + (optional) + + For OpenID Connect Implicit Flow only. + The issuer of the id_token. It must exactly match the iss claim (if exists) in the id_token +
+ subject + (optional) + + For OpenID Connect Implicit Flow only. + The subject of the id_token. The detailed meaning is usually application specific. + It must exactly match the sub claim (if exists) in the id_token +
+ pub-key + (optional) + + For OpenID Connect Implicit Flow only. + The public key to verify the id_token signature. It could be .pem format or JWK format.
+ For signing algorithm (usually specified by alg in the id_token header), currently only RS256, RS384, or RS512 is supported.
+ If not set, then the id_token itself should carry the public key, or the url which can be used to retrieve the public key. +
+ +
+
+ + +
+
+

Customizations

+ +

+ The oauth directive comes to life with customization in mind. You can easily define + personalized CSS styles or create brand new templates. +

+ +

Custom CSS

+ +

+ By default the directive is shown as a simple link. To improve its visual style + add some CSS rules. +

+ + + + + + + + + + + + + + + + + + + + +
+ + Default + + + + (no extra CSS required) + +
+ Logged out + + + Sign In
+
+
+ Logged in + + + Logout Alice + +
+ Access denied + + + Access denied. Try again. + +
+ + + + + + + + + + + + + + + + + + + + +
+ + Blue button style + + + + <Download CSS> + +
+ Logged out + + + Sign In + +
+ Logged in + + + Logout Alice
+
+
+ Access denied + + + Access denied. Try again. + +
+ +

+ All CSS rules applies to the following HTML structure. +

+ +
<span class="oauth">
+  <a href="#" class="logged-out">Sign In</a>
+  <a href="#" class="logged-in">Logout Alice</a>
+  <a href="#" class="denied">Access denied. Try again.</a>
+</span>
+ +

+ It's easy to create your own. Base all of your rules on the oauth class + or checkout existing CSS definitions to better understand how it works + (e.g. the blue button CSS). +

+ +

+ Mail or Tweet + me if you make new CSS buttons so that we can add them do the docs.

+

+ + +

Custom Template

+ +

+ When the custom template does not satisfy your needs (e.g. you want to show the user email + when logged in), you can create a new one. To have an idea of what a template looks like, + see the code below (related to the default template). +

+ +
<span class="oauth">
+  <a href="#" class="logged-out" ng-show="show=='logged-out'" ng-click="login()">{{text}}</a>
+  <a href="#" class="logged-in"  ng-show="show=='logged-in'"  ng-click="logout()">Logout {{profile.email}}</a>
+  <a href="#" class="denied"     ng-show="show=='denied'"     ng-click="login()">Access denied. Try again.</a>
+</span>
+ +

+ To create your new template, define the HTML and make it work using the following internal API. +

+ + + + + + + + + + + + + + + + + + + + +
+ show + variable + + Defines the oauth status.
+ Valid values are logged-out, logged-in and denied. +
+ text + variable + + Visible text when the widget is logged out.
+ Default to Sign in. +
+ login() + method + + Logs in redirecting the app to the OAuth 2.0 authorization server. +
+ logout() + method + + Logs out deleting the access token. +
+ +

+ Now, supposing you have defined a new template in views/templates/custom.html, + you need to let the directive know about its existence. You can do this in two ways. +

+ +
    +
  • Setting the template attribute
  • +
  • Firing the template event
  • +
+ +

1 - Template attribute

+ +

+ Set the template path (or uri) as value for the template attribute. +

+ +
<oauth
+  template="views/templates/custom.html"
+  client-id="<client-id>"
+  redirect-uri="<redirect-uri>"
+  profile-uri="<profile-uri>"
+  scope="<your-scope>">
+</oauth>
+ +

2 - Template event

+ +

+ Fires the update template event passing the template path (or uri) as param. +

+ +
$rootScope.$broadcast('oauth:template:update', 'views/templates/custom.html');
+ +

+ Use this solution when you need to update the directive at runtime. For all + other cases, use the template attribute. +

+ +
+
+ + +
+
+

Events

+ +

+ The events related to the oauth directive are the following. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ oauth:login(token) + fired + + Fired when the user has completed the login flow, and authorized the third party app. +
+ oauth:authorized(token) + fired + + Fired when the view is initializing and user has a non-expired auth token in the local session storage. +
+ oauth:logout + fired + + Fired when the user logs out. +
+ oauth:loggedOut + fired + + Fired when the user is not logged in. +
+ oauth:denied + fired + + Fired when the user denies the access to the third party app. +
+ oauth:expired + fired + + Fired when the access token is expired. +
+ oauth:template:update(uri) + listening + + Listening for runtime the template change.
+ See examples in the customization section. +
+ oauth:profile(profile) + fired + + Fired when the profile data has been retrieved.
+ Does not fire if there is no profile-uri parameter. +
+ +

+ Here some examples. +

+ +
$scope.$on('oauth:login', function(event, token) {
+  console.log('Authorized third party app with token', token.access_token);
+});
+
+$scope.$on('oauth:logout', function(event) {
+  console.log('The user has signed out');
+});
+
+$scope.$on('oauth:loggedOut', function(event) {
+  console.log('The user is not signed in');
+});
+
+$scope.$on('oauth:denied', function(event) {
+  console.log('The user did not authorize the third party app');
+});
+
+$scope.$on('oauth:expired', function(event) {
+  console.log('The access token is expired. Please refresh.');
+});
+
+$scope.$on('oauth:profile', function(profile) {
+  console.log('User profile data retrieved: ', profile);
+});
+ + +
+
+ + +
+
+

Logged in or logged out?

+ +

+ Inject the AccessToken.get() service to understand if the user is logged in or out. +

+ +
$timeout(function() {
+  $scope.logged = !!AccessToken.get();
+}, 0)
+ +

+ The AccessToken.get() method returns null when the user is logged + out and the access token representation when the user is logged in (see below). +

+ + +
{
+  "access_token": "9ce03e06f037180c2dcfa8b278217eb56d747730c69xxx",
+  "token_type": "bearer",
+  "expires_in": 7200,
+  "state": "remember-me"
+}
+ +
+
+ + +
+
+

Profile resource

+ +

+ When a user signs in, the profile is accessible through the Profile service. +

+ +
$scope.profile = Profile.get();
+ +

+ This profile is accessible only when the profile-uri attribute is defined and + it sets the Authorization header with the access token to authenticate the request. +

+ +
Authorization:Bearer {token}
+ +
+
+ + +
+
+

Links

+ + + +
+
+ + +
+
+

Thanks

+ +

+ Mail or + Tweet + me for any idea that can improve the project. +

+ +

+ This project was created and released as open-source thanks to Lelylan, + a new platform to monitor and control your devices through a simple, open and robust REST API. +

+ +

+ If you like what I'm doing offer me a coffe + +

+ +
+
+ + diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 28aab8c..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,64 +0,0 @@ -// Karma configuration -// http://karma-runner.github.io/0.10/config/configuration-file.html - -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'], - - // list of files / patterns to load in the browser - files: [ - 'app/bower_components/jquery/jquery.js', - 'app/bower_components/angular/angular.js', - 'app/bower_components/angular-mocks/angular-mocks.js', - 'app/bower_components/ngstorage/ngStorage.js', - 'app/bower_components/timecop/timecop-0.1.1.js', - 'app/scripts/*.js', - 'app/scripts/**/*.js', - 'app/views/**/*.html', - 'test/spec/services/**/*.js', - 'test/spec/directives/**/*.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, - - // Start these browsers, currently available: - // - Chrome - // - ChromeCanary - // - Firefox - // - Opera - // - Safari (only Mac) - // - PhantomJS - // - IE (only Windows) - browsers: ['PhantomJS'], - - // Continuous Integration mode - // if true, it capture browsers, run tests and exit - singleRun: false, - - - // Preprocessor for converting HTML files to AngularJS templates - preprocessors: { 'app/views/**/*.html': ['html2js'] }, - - // set the path to use to search the template and set the templates module to - // load all templates at once - ngHtml2JsPreprocessor: { - stripPrefix: 'app/', - moduleName: 'templates' - }, - }); -}; diff --git a/package.json b/package.json deleted file mode 100644 index fb41b7b..0000000 --- a/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "oauth-ng", - "version": "0.1.0", - "author": "Andrea Reginato ", - "description": "AngularJS Directive for OAuth 2.0", - "repository": { - "type": "git", - "url": "https://github.com/andreareginato/oauth-ng" - }, - "dependencies": {}, - "devDependencies": { - "grunt": "~0.4.1", - "grunt-autoprefixer": "~0.4.0", - "grunt-bower-install": "~0.7.0", - "grunt-concurrent": "~0.4.1", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-compass": "~0.6.0", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-connect": "~0.5.0", - "grunt-contrib-copy": "~0.4.1", - "grunt-contrib-cssmin": "~0.7.0", - "grunt-contrib-htmlmin": "~0.1.3", - "grunt-contrib-imagemin": "~0.3.0", - "grunt-contrib-jshint": "~0.7.1", - "grunt-contrib-uglify": "~0.2.0", - "grunt-contrib-watch": "~0.5.2", - "grunt-google-cdn": "~0.2.0", - "grunt-newer": "~0.5.4", - "grunt-ngmin": "~0.0.2", - "grunt-rev": "~0.1.0", - "grunt-svgmin": "~0.2.0", - "grunt-usemin": "~2.0.0", - "jshint-stylish": "~0.1.3", - "load-grunt-tasks": "~0.2.0", - "time-grunt": "~0.2.1", - "karma-ng-scenario": "~0.1.0", - "grunt-karma": "~0.8.0", - "karma": "~0.12.0", - "karma-ng-html2js-preprocessor": "~0.1.0", - "karma-jasmine": "~0.2.2", - "karma-phantomjs-launcher": "~0.1.2", - "karma-chrome-launcher": "~0.1.2", - "karma-coverage": "~0.2.1", - "grunt-protractor-runner": "~0.2.4", - "grunt-replace": "~0.7.7", - "grunt-string-replace": "~0.2.7" - }, - "engines": { - "node": ">=0.8.0" - }, - "scripts": { - "test": "grunt test:unit" - } -} diff --git a/public/icons/LICENSE.txt b/public/icons/LICENSE.txt new file mode 100644 index 0000000..21877ea --- /dev/null +++ b/public/icons/LICENSE.txt @@ -0,0 +1,21 @@ +Font license info + + +## Font Awesome + + Copyright (C) 2012 by Dave Gandy + + Author: Dave Gandy + License: SIL () + Homepage: http://fortawesome.github.com/Font-Awesome/ + + +## Typicons + + (c) Stephen Hutchings 2012 + + Author: Stephen Hutchings + License: CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0/) + Homepage: http://typicons.com/ + + diff --git a/public/icons/README.txt b/public/icons/README.txt new file mode 100644 index 0000000..43e23f2 --- /dev/null +++ b/public/icons/README.txt @@ -0,0 +1,75 @@ +This webfont is generated by http://fontello.com open source project. + + +================================================================================ +Please, note, that you should obey original font licences, used to make this +webfont pack. Details available in LICENSE.txt file. + +- Usually, it's enough to publish content of LICENSE.txt file somewhere on your + site in "About" section. + +- If your project is open-source, usually, it will be ok to make LICENSE.txt + file publically available in your repository. + +- Fonts, used in Fontello, don't require to make clickable links on your site. + But any kind of additional authors crediting is welcome. +================================================================================ + + +Comments on archive content +--------------------------- + +- /font/* - fonts in different formats + +- /css/* - different kinds of css, for all situations. Should be ok with + twitter bootstrap. Also, you can skip style and assign icon classes + directly to text elements, if you don't mind about IE7. + +- demo.html - demo file, to show your webfont content + +- LICENSE.txt - license info about source fonts, used to build your one. + +- config.json - keeps your settings. You can import it back to fontello anytime, + to continue your work + + +Why so many CSS files ? +----------------------- + +Because we like to fit all your needs :) + +- basic file, .css - is usually enougth, in contains @font-face + and character codes definition + +- *-ie7.css - if you need IE7 support, but still don't wish to put char codes + directly into html + +- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face + rules, but still wish to benefit of css generation. That can be very + convenient for automated assets build systems. When you need to update font - + no needs to manually edit files, just override old version with archive + content. See fontello source codes for example. + +- *-embedded.css - basic css file, but with embedded WOFF font, to avoid + CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. + We strongly recommend to resolve this issue by `Access-Control-Allow-Origin` + server headers. But if you ok with dirty hack - this file is for you. Note, + that data url moved to separate @font-face to avoid problems with + + + + + + + + +
+

+ slate + font demo +

+ +
+
+
+
icon-cancel0xe803
+
icon-menu-10xe811
+
icon-plus0xe804
+
icon-plus-circled0xe805
+
+
+
icon-minus0xe800
+
icon-minus-circled0xe806
+
icon-attention-alt0xe809
+
icon-attention0xe808
+
+
+
icon-attention-circled0xe80a
+
icon-cancel-circled0xe802
+
icon-cog0xe807
+
icon-resize-full0xe810
+
+
+
icon-signal0xe80b
+
icon-smile0xe80c
+
icon-frown0xe80d
+
icon-meh0xe80e
+
+
+
icon-github0xe80f
+
icon-menu0xe801
+
+
+ + + \ No newline at end of file diff --git a/public/icons/font/slate.eot b/public/icons/font/slate.eot new file mode 100644 index 0000000..2f3f7be Binary files /dev/null and b/public/icons/font/slate.eot differ diff --git a/public/icons/font/slate.svg b/public/icons/font/slate.svg new file mode 100644 index 0000000..1176f69 --- /dev/null +++ b/public/icons/font/slate.svg @@ -0,0 +1,29 @@ + + + +Copyright (C) 2013 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/font/slate.ttf b/public/icons/font/slate.ttf new file mode 100644 index 0000000..31b3777 Binary files /dev/null and b/public/icons/font/slate.ttf differ diff --git a/public/icons/font/slate.woff b/public/icons/font/slate.woff new file mode 100644 index 0000000..a52c7c8 Binary files /dev/null and b/public/icons/font/slate.woff differ diff --git a/public/images/application.png b/public/images/application.png new file mode 100644 index 0000000..dc7db91 Binary files /dev/null and b/public/images/application.png differ diff --git a/public/images/demo.png b/public/images/demo.png new file mode 100644 index 0000000..2bb41aa Binary files /dev/null and b/public/images/demo.png differ diff --git a/public/images/result.png b/public/images/result.png new file mode 100644 index 0000000..db8a5a8 Binary files /dev/null and b/public/images/result.png differ diff --git a/public/images/yeoman.png b/public/images/yeoman.png new file mode 100644 index 0000000..f32abfb Binary files /dev/null and b/public/images/yeoman.png differ diff --git a/public/oauth-buttons.css b/public/oauth-buttons.css new file mode 100644 index 0000000..7ad29b4 --- /dev/null +++ b/public/oauth-buttons.css @@ -0,0 +1,30 @@ +.blue-button .oauth a { + text-decoration: none; + background: #239cbb; + box-sizing: border-box; + min-height: 35px; + width: auto; + display: inline-block; + padding: 0.9em 1.37em; + cursor: pointer; + color: #fff !important; + font-size: 12px; + line-height: 13px; + font-weight: 300; + text-align: center; + letter-spacing: 1px; + text-transform: uppercase; + text-shadow: none; + border-radius: 0.2em; + border: rgba(0,0,0,0.05) 0.1em solid; + -webkit-transition: background 0.3s ease, border-color 0.3s ease; + -moz-transition: background 0.3s ease, border-color 0.3s ease; + transition: background 0.3s ease, border-color 0.3s ease; +} + +.blue-button .oauth a:hover { + background: #01cf9e; + color: #fff; + text-decoration: none; +} + diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..87aa822 --- /dev/null +++ b/public/style.css @@ -0,0 +1,302 @@ + +* { + -webkit-box-sizing: border-box; +} + +body, html { + margin: 0; + padding: 0; + height: 100%; +} + +body { + font: 17px/1.5 "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; + background: white; + margin: 0; + color: #33333d; + overflow-y: scroll; + overflow-x: hidden; +} + +a { + color: #000; +} + +section { + min-height: 50%; +} + +section:not(:last-child) { + border-bottom: 1px solid #eee; +} + +section:not(#top) { + padding: 150px 100px; + text-align: center; +} + +pre { + margin: 2em 0; + background: white; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + padding: 25px; + font-size: .9em; + font-family: monospace; + overflow-x: auto; +} + +xmp { + margin: 0; +} + +.content img { + width: 100%; + border: 1px solid #ddd; + padding: 1em; + margin: 2em 0; + border-radius: 5px; +} + +p { + font-weight: 300; + font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; +} + +code { + font-family: monospace; + font-size: .9em; +} + +h1 { + font-size: 2em; + margin-top: 0em; + padding-top: 1em; +} + +h1:not(:first-child) { + margin-top: 50px; +} + +h2 { + margin-top: 80px; + font-size: 1.2em; +} + +h3 { + margin-top: 40px; + font-size: 1em; +} + +ul li { + padding: 0.1em 0; + /*font-size: .8em;*/ +} + +section:nth-child(2n) { + background: #fdfdfd; + box-shadow: inset 0 1px 0 0 white; +} + +.content { + margin: 0 auto; + max-width: 750px; + text-align: left; +} + +#top { + height: 100%; +} + +#heading { + -webkit-user-select: none; + position: absolute; + top: 50%; + margin-top: -150px; + text-align: center; + width: 100%; +} + +#logo { + font: 100px 'Italiana', sans-serif; + text-transform: lowercase; +} + +#tagline { + font-size: 16px; +} + +.github-img { + width: 100px; +} + +#menu { + position: fixed; + top: 35px; + right: 30px; + z-index: 50; +} + +#menu:hover ul { + display: block; +} + +#menu a { + color: inherit; +} + +#menu a#toggle { + position: absolute; + top: 0; + right: 0; + padding: 5px; + background: rgba(255,255,255,.9); + border-radius: 2px; + border: 1px solid transparent; + z-index: 5; +} + +#menu:hover a#toggle { + border: 1px solid #efefef; + border-bottom: none; +} + +#menu i.icon-menu { + font-size: 1.5em; +} + +#menu ul { + display: none; + position: absolute; + width: 200px; + top: 2.8em; + right: 0; + margin: 0; + border: 1px solid #efefef; + border-bottom: 1px solid #ddd; + border-radius: 2px; + padding: 25px; + background: rgba(255,255,255,.95); + box-shadow: 0 1px 3px 0 #eee; +} + +#menu ul li { + list-style: none; +} + +#menu ul li a { + display: block; + text-decoration: none; + padding: 3px 0; +} + +#menu ul li a:hover { + text-decoration: underline; +} + +ul.task-list { + list-style-type: none; + padding-left:10px; +} + +ul.task-list .task-list-item-checkbox { + margin-right: 10px; +} + +@media screen and (max-width: 700px) { + section:not(#top) { + padding: 50px 25px; + } + + #heading { + margin-top: -75px; + } + + #logo { + font-size: 75px; + } + + #tagline { + font-size: 12px; + } +} + +@media screen and (max-width: 500px) { + body { + font-size: 13px; + } +} + +/* * * * * * * * */ +/* Table Listing */ +/* * * * * * * * */ + +table { + margin: 40px 0; + width: 100%; +} + +table tr th { + border-bottom: 1px solid #dddddd !important; + text-transform: uppercase; + font-style: italic; +} + +table tr td, +table tr th { + color: #333; + font-weight: 200; + line-height: 1.5 !important; + border-bottom: 1px solid #eee; + padding: 12px 0; + vertical-align: top; +} + +table .parameter { + width: 35%; +} + +table .smaller-titles .parameter { + width: 25%; +} + +table .extra { + width: 30% !important; +} + +table .nested { + padding-left: 30px !important; +} + +table span.info { + display: block; + color: #888; + font-weight: 200; +} + +table.button .title { + text-transform: uppercase; + font-weight: 600; +} + +table.button .parameter { + width: 100px; +} + +.notice { + background-color: #f4f4f4; + padding: 1em; + border: 1px solid #e4e4e4; +} + +.btn { + display: inline-block; + text-decoration: none; + border: 1px solid #000; + padding: 0.5em 1em; + margin-right: 0.5em; +} + +.btn-group { + margin-top: 2em; +} diff --git a/test/runner.html b/test/runner.html deleted file mode 100644 index f4a00a1..0000000 --- a/test/runner.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - End2end Test Runner - - - - - - \ No newline at end of file diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js deleted file mode 100644 index 0f33f44..0000000 --- a/test/spec/directives/oauth.js +++ /dev/null @@ -1,259 +0,0 @@ -'use strict'; - -describe('oauth', function() { - - var $rootScope, $location, $sessionStorage, $httpBackend, $compile, AccessToken, Endpoint, element, scope, result, callback; - - var uri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http://example.com/redirect&scope=scope&state=/'; - var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; - var denied = 'error=access_denied&error_description=error'; - var headers = { 'Accept': 'application/json, text/plain, */*', 'Authorization': 'Bearer token' } - var profile = { id: '1', full_name: 'Alice Wonderland', email: 'alice@example.com' }; - - beforeEach(module('oauth')); - beforeEach(module('templates')); - - beforeEach(inject(function($injector) { $rootScope = $injector.get('$rootScope') })); - beforeEach(inject(function($injector) { $compile = $injector.get('$compile') })); - beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); - beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend') })); - beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); - beforeEach(inject(function($injector) { Endpoint = $injector.get('Endpoint') })); - beforeEach(inject(function($injector) { callback = jasmine.createSpy('callback') })); - - beforeEach(inject(function($rootScope, $compile) { - element = angular.element( - '' + - 'Sign In' + - '' - ); - })); - - var compile = function() { - scope = $rootScope; - $compile(element)(scope); - scope.$digest(); - } - - - describe('when logged in', function() { - - beforeEach(function() { - $location.hash(fragment); - }); - - beforeEach(function() { - $httpBackend.whenGET('http://example.com/me', headers).respond(profile); - }); - - beforeEach(function() { - $rootScope.$on('oauth:success', callback); - }); - - beforeEach(function() { - compile($rootScope, $compile); - }); - - it('shows the link "Logout #{profile.email}"', function() { - $rootScope.$apply(); - $httpBackend.flush(); - result = element.find('.logged-in').text(); - expect(result).toBe('Logout Alice Wonderland'); - }); - - it('removes the fragment', function() { - expect($location.hash()).toBe(''); - }); - - it('shows the logout link', function() { - expect(element.find('.logged-out').attr('class')).toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).not.toMatch('ng-hide'); - }); - - it('fires the oauth:login event', function() { - var event = jasmine.any(Object); - var token = AccessToken.get(); - expect(callback).toHaveBeenCalledWith(event, token); - }); - - describe('when refreshes the page', function() { - - beforeEach(function() { - $rootScope.$on('oauth:success', callback); - }); - - beforeEach(function() { - $location.path('/'); - }); - - beforeEach(function() { - compile($rootScope, $compile); - }); - - it('keeps being logged in', function() { - $rootScope.$apply(); - $httpBackend.flush(); - result = element.find('.logged-in').text(); - expect(result).toBe('Logout Alice Wonderland'); - }); - - it('shows the logout link', function() { - expect(element.find('.logged-out').attr('class')).toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).not.toMatch('ng-hide'); - }); - - it('fires the oauth:login event', function() { - var event = jasmine.any(Object); - var token = AccessToken.get(); - expect(callback).toHaveBeenCalledWith(event, token); - }); - }); - - - describe('when logs out', function() { - - beforeEach(function() { - $rootScope.$on('oauth:logout', callback); - }); - - beforeEach(function() { - element.find('.logged-in').click(); - }); - - it('shows the login link', function() { - expect(element.find('.logged-out').attr('class')).not.toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); - }); - - it('fires the oauth:logout event', function() { - var event = jasmine.any(Object); - expect(callback).toHaveBeenCalledWith(event); - }); - }); - }); - - - describe('when logged out', function() { - - beforeEach(function() { - $rootScope.$on('oauth:logout', callback); - }); - - beforeEach(function() { - AccessToken.destroy(); - }); - - beforeEach(function() { - compile($rootScope, $compile) - }); - - beforeEach(function() { - spyOn(Endpoint, 'redirect'); - }); - - it('shows the text "Sing In"', function() { - result = element.find('.logged-out').text(); - expect(result).toBe('Sign In'); - }); - - it('sets the href attribute', function() { - result = element.find('.logged-out').click(); - expect(Endpoint.redirect).toHaveBeenCalled(); - }); - - it('shows the login link', function() { - expect(element.find('.logged-out').attr('class')).not.toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); - }); - - it('fires the oauth:logout event', function() { - var event = jasmine.any(Object); - expect(callback).toHaveBeenCalledWith(event); - }); - }); - - - describe('when denied', function() { - - beforeEach(function() { - $location.hash(denied); - }); - - beforeEach(function() { - $rootScope.$on('oauth:denied', callback); - }); - - beforeEach(function() { - compile($rootScope, $compile) - }); - - beforeEach(function() { - spyOn(Endpoint, 'redirect'); - }); - - it('shows the text "Denied"', function() { - result = element.find('.denied').text(); - expect(result).toBe('Access denied. Try again.'); - }); - - it('sets the href attribute', function() { - result = element.find('.denied').click(); - expect(Endpoint.redirect).toHaveBeenCalled(); - }); - - it('shows the login link', function() { - expect(element.find('.logged-out').attr('class')).toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); - expect(element.find('.denied').attr('class')).not.toMatch('ng-hide'); - }); - - it('fires the oauth:denied event', function() { - var event = jasmine.any(Object); - expect(callback).toHaveBeenCalledWith(event); - }); - }); - - - describe('with no custom template', function() { - - beforeEach(function() { - AccessToken.destroy(); - }); - - beforeEach(function() { - compile($rootScope, $compile) - }); - - it('shows the default template', function() { - expect(element.find('.btn-oauth').text()).toBe(''); - }); - }); - - - describe('with custom template', function() { - - beforeEach(function() { - AccessToken.destroy(); - }); - - beforeEach(function() { - compile($rootScope, $compile) - }); - - beforeEach(function() { - $rootScope.$broadcast('oauth:template:update', 'views/templates/button.html'); - $rootScope.$apply(); - }); - - it('shows the button template', function() { - expect(element.find('.oauth .logged-out').text()).toBe('Login Button'); - }); - }); -}); diff --git a/test/spec/services/access-token.js b/test/spec/services/access-token.js deleted file mode 100644 index 508a32d..0000000 --- a/test/spec/services/access-token.js +++ /dev/null @@ -1,196 +0,0 @@ -'use strict'; - -describe('AccessToken', function() { - - var result, $location, $sessionStorage, AccessToken, date; - - var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; - var denied = 'error=access_denied&error_description=error'; - var token = { access_token: 'token', token_type: 'bearer', expires_in: 7200, state: '/path' }; - - beforeEach(module('oauth')); - - beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); - beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); - - - describe('#set', function() { - - describe('when sets the access token', function() { - - beforeEach(function() { - $location.hash(fragment); - }); - - beforeEach(function() { - result = AccessToken.set(); - }); - - it('sets the access token', function() { - expect(result.access_token).toEqual('token'); - }); - - it('sets #expires_at', function() { - var expected_date = new Date(); - expected_date.setSeconds(expected_date.getSeconds() + 7200 - 60); - expect(parseInt(result.expires_at/100)).toBe(parseInt(expected_date/100)); // 10 ms - }); - }); - - describe('with the access token in the fragment URI', function() { - - beforeEach(function() { - $location.hash(fragment); - }); - - beforeEach(function() { - result = AccessToken.set(); - }); - - it('sets the access token', function() { - expect(result.access_token).toEqual('token'); - }); - - it('removes the fragment string', function() { - expect($location.hash()).toEqual(''); - }); - - it('stores the token in the session', function() { - var stored_token = $sessionStorage.token; - expect(result.access_token).toEqual('token'); - }); - }); - - describe('with the access token stored in the session', function() { - - beforeEach(function() { - $sessionStorage.token = token; - }); - - beforeEach(function() { - result = AccessToken.set(); - }); - - it('sets the access token from session', function() { - expect(result.access_token).toEqual('token'); - }); - }); - - describe('with the denied message in the fragment URI', function() { - - beforeEach(function() { - $location.hash(denied); - }); - - beforeEach(function() { - result = AccessToken.set(); - }); - - it('sets the access token', function() { - expect(result.error).toEqual('access_denied'); - }); - - it('removes the fragment string', function() { - expect($location.hash()).toEqual(''); - }); - - it('stores the error message in the session', function() { - var stored_token = $sessionStorage.token; - expect(result.error).toBe('access_denied'); - }); - }); - }); - - - describe('#get', function() { - - beforeEach(function() { - $location.hash(fragment); - }); - - beforeEach(function() { - AccessToken.set(); - }); - - beforeEach(function() { - result = AccessToken.get(); - }); - - it('sets the access token', function() { - expect(result.access_token).toEqual('token'); - }); - }); - - - describe('#destroy', function() { - - beforeEach(function() { - $location.hash(fragment); - }); - - beforeEach(function() { - AccessToken.set(); - }); - - beforeEach(function() { - result = AccessToken.destroy(); - }); - - it('sets the access token', function() { - expect(result).toBeNull(); - }); - - it('reset the cache', function() { - expect($sessionStorage.token).toBeUndefined; - }); - }); - - - describe('#expired', function() { - - beforeEach(function() { - $location.hash(fragment); - }); - - beforeEach(function() { - AccessToken.set(); - }); - - describe('when not expired', function() { - - beforeEach(function() { - result = AccessToken.expired(); - }); - - it('returns false', function() { - expect(result).toBe(false); - }); - }); - - describe('when expired', function() { - - beforeEach(function() { - date = new Date(); - date.setTime(date.getTime() + 86400000); - }); - - beforeEach(function() { - Timecop.install(); - Timecop.travel(date); // go to the future for one day - }); - - beforeEach(function() { - result = AccessToken.expired(); - }); - - afterEach(function() { - Timecop.uninstall(); - }); - - it('returns true', function() { - expect(result).toBe(true); - }); - }); - }); -}); diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js deleted file mode 100644 index 352b4a8..0000000 --- a/test/spec/services/endpoint.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -describe('Endpoint', function() { - - var result, $location, $sessionStorage, Endpoint; - - var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; - var params = { site: 'http://example.com/', clientId: 'client-id', redirectUri: 'http://example.com/redirect', scope: 'scope', authorizePath: '/oauth/authorize' }; - var uri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http://example.com/redirect&scope=scope&state=/'; - - beforeEach(module('oauth')); - - beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); - beforeEach(inject(function($injector) { Endpoint = $injector.get('Endpoint') })); - - - describe('#set', function() { - - beforeEach(function() { - result = Endpoint.set(params); - }); - - it('returns the oauth server endpoint', function() { - expect(result).toEqual(uri); - }); - - describe('when in a specific /path', function() { - - beforeEach(function() { - $location.path('/path'); - }); - - beforeEach(function() { - result = Endpoint.set(params); - }); - - it('returns previous path in status', function() { - expect(result).toEqual(uri + 'path'); - }); - }); - }); - - - describe('#get', function() { - - beforeEach(function() { - Endpoint.set(params); - }); - - beforeEach(function() { - result = Endpoint.get(); - }); - - it('returns the oauth server endpoint', function() { - expect(result).toEqual(uri); - }); - }); -}); diff --git a/test/spec/services/profile.js b/test/spec/services/profile.js deleted file mode 100644 index 3a5e330..0000000 --- a/test/spec/services/profile.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -describe('Profile', function() { - - var $rootScope, $location, $httpBackend, $http, AccessToken, result, date, callback; - - var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; - var headers = { 'Accept': 'application/json, text/plain, */*', 'Authorization': 'Bearer token' } - var params = { site: 'http://example.com/', client: 'client-id', redirect: 'http://example.com/redirect', scope: 'scope', profileUri: 'http://example.com/me' }; - var resource = { id: '1', name: 'Alice' }; - - beforeEach(module('oauth')); - - beforeEach(inject(function($injector) { $rootScope = $injector.get('$rootScope') })); - beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend') })); - beforeEach(inject(function($injector) { $http = $injector.get('$http') })); - beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); - - beforeEach(function() { callback = jasmine.createSpy('callback') }); - - - describe('.get', function() { - - describe('when authenticated', function() { - - beforeEach(function() { - $location.hash(fragment); - AccessToken.set(params); - }); - - beforeEach(function() { - $httpBackend.whenGET('http://example.com/me', headers).respond(resource); - }); - - it('makes the request', inject(function(Profile) { - $httpBackend.expect('GET', 'http://example.com/me'); - Profile.get(params.profileUri); - $rootScope.$apply(); - $httpBackend.flush(); - })); - - it('gets the resource', inject(function(Profile) { - Profile.get(params.profileUri).success(function(response) { result = response }); - $rootScope.$apply(); - $httpBackend.flush(); - expect(result.name).toEqual('Alice'); - })); - - - describe('when expired', function() { - - beforeEach(function() { - $rootScope.$on('oauth:expired', callback); - }); - - beforeEach(function() { - date = new Date(); - date.setTime(date.getTime() + 86400000); - }); - - beforeEach(function() { - Timecop.install(); - Timecop.travel(date); // go one day in the future - }); - - beforeEach(function() { - }) - - afterEach(function() { - Timecop.uninstall(); - }); - - it('fires the oauth:expired event', inject(function(Profile) { - Profile.get(params.profileUri); - $rootScope.$apply(); - $httpBackend.flush(); - var event = jasmine.any(Object); - var token = jasmine.any(Object); - expect(callback).toHaveBeenCalledWith(event, token); - })); - }); - }); - }); -});