Skip to content

Commit

Permalink
Fix bugs and add sample
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldijou committed Sep 16, 2014
1 parent 57c732a commit 155b4cd
Show file tree
Hide file tree
Showing 31 changed files with 750 additions and 105 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.history
target
project/project
project/target
15 changes: 15 additions & 0 deletions examples/play-angular/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
logs
project/project
project/target
target
tmp
.history
dist
/.idea
/*.iml
/out
/.idea_modules
/.classpath
/.project
/RUNNING_PID
/.settings
1 change: 1 addition & 0 deletions examples/play-angular/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Example: Play Framework + AngularJS
50 changes: 50 additions & 0 deletions examples/play-angular/app/controllers/Application.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package controllers

import play.api._
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._

import pdi.scala.jwt._

import models.User

object Application extends Controller with Secured {
val passwords = Seq("red", "blue", "green", "purple", "totoro")

def index = Action {
Ok(views.html.index())
}

private val loginForm: Reads[(String, String)] =
(JsPath \ "username").read[String] and
(JsPath \ "password").read[String] tupled

def login = Action(parse.json) { implicit request =>
request.body.validate(loginForm).fold(
errors => {
BadRequest(JsError.toFlatJson(errors))
},
form => {
if (passwords.contains(form._2)) {
Ok.addingToJwtSession("user", User(form._1))
} else {
Unauthorized
}
}
)
}

def publicApi = Action {
Ok("That was easy!")
}

def privateApi = Authenticated {
Ok("Only the best can see that.")
}

def adminApi = Admin {
Ok("Top secret data. Hopefully, nobody will ever access it.")
}

}
36 changes: 36 additions & 0 deletions examples/play-angular/app/controllers/Secured.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package controllers

import scala.concurrent.Future
import play.api.libs.concurrent.Execution.Implicits._

import play.api.mvc._
import play.api.mvc.Results._
import play.api.libs.json._

import pdi.scala.jwt._

import models.User

class AuthenticatedRequest[A](val user: User, request: Request[A]) extends WrappedRequest[A](request)

trait Secured {
def Authenticated = AuthenticatedAction
def Admin = AdminAction
}

object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: AuthenticatedRequest[A] => Future[SimpleResult]) =
request.jwtSession(request).getAs[User]("user") match {
case Some(user) => block(new AuthenticatedRequest(user, request)).map(_.refreshJwtSession(request))
case _ => Future.successful(Unauthorized)
}
}

object AdminAction extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: AuthenticatedRequest[A] => Future[SimpleResult]) =
request.jwtSession(request).getAs[User]("user") match {
case Some(user) if user.isAdmin => block(new AuthenticatedRequest(user, request)).map(_.refreshJwtSession(request))
case Some(_) => Future.successful(Forbidden.refreshJwtSession(request))
case _ => Future.successful(Unauthorized)
}
}
12 changes: 12 additions & 0 deletions examples/play-angular/app/models/User.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package models

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class User(name: String) {
def isAdmin: Boolean = (name.toLowerCase == "admin")
}

object User {
implicit val userFormat = Json.format[User]
}
46 changes: 46 additions & 0 deletions examples/play-angular/app/views/index.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@()

<!DOCTYPE html>

<html ng-app="app">
<head>
<title>JWT - Play Angular</title>
<link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/app.css")">
</head>
<body ng-controller="HomeCtrl as ctrl">
<header>

</header>

<section ng-hide="authenticated">
<form ng-submit="ctrl.login()">
<input type="text" placeholder="Username" ng-model="ctrl.loginForm.username">
<input type="password" placeholder="Password ('red' or 'blue' or 'green')" ng-model="ctrl.loginForm.password">
<button class="btn btn-login" type="submit">Login</button>
</form>
</section>

<section ng-show="authenticated">
<div>Hello there! How are you today <strong>{{user().name}}</strong>?</div>
<button type="button" class="btn btn-logout" ng-click="ctrl.logout()">Logout</button>
</section>

<section>
<button class="btn btn-public" type="button" ng-click="ctrl.publicCall()">Public API</button>
<button class="btn btn-private" type="button" ng-click="ctrl.privateCall()">Private API</button>
<button class="btn btn-admin" type="button" ng-click="ctrl.adminCall()">Admin API</button>
</section>

<section ng-if="notification" class="fade-if" ng-click="closeNotification()">
<div class="notification" ng-class="'' + notification.severity">
{{notification.message}}
</div>
</section>

<script src="//rawgit.com/pauldijou/jwt-client/master/jwt-client.js" type="text/javascript"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.0/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.0/angular-animate.js"></script>
<script src="@routes.Assets.at("javascripts/app.js")" type="text/javascript"></script>
</body>
</html>
62 changes: 62 additions & 0 deletions examples/play-angular/conf/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# This is the main configuration file for the application.
# ~~~~~

# Secret key
# ~~~~~
# The secret key is used to secure cryptographics functions.
#
# This must be changed for production, but we recommend not changing it in this file.
#
# See http://www.playframework.com/documentation/latest/ApplicationSecret for more details.
application.secret="?`wdc6<s]M=Z4VG][lSu1d48pxr;`FvQOl4H^XfvAJX5G<9W`jmSi/?XaFvT9hGb"

# The application languages
# ~~~~~
application.langs="en"

# Global object class
# ~~~~~
# Define the Global object class for this application.
# Default to Global in the root package.
# application.global=Global

# Router
# ~~~~~
# Define the Router object to use for this application.
# This router will be looked up first when the application is starting up,
# so make sure this is the entry point.
# Furthermore, it's assumed your route file is named properly.
# So for an application router like `my.application.Router`,
# you may need to define a router file `conf/my.application.routes`.
# Default to Routes in the root package (and conf/routes)
# application.router=my.application.Routes

# Database configuration
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
#
# db.default.driver=org.h2.Driver
# db.default.url="jdbc:h2:mem:play"
# db.default.user=sa
# db.default.password=""

# Evolutions
# ~~~~~
# You can disable evolutions if needed
# evolutionplugin=disabled

# Logger
# ~~~~~
# You can also configure logback (http://logback.qos.ch/),
# by providing an application-logger.xml file in the conf directory.

# Root logger:
logger.root=ERROR

# Logger used by the framework:
logger.play=INFO

# Logger provided to your application:
logger.application=DEBUG

14 changes: 14 additions & 0 deletions examples/play-angular/conf/routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET / controllers.Application.index

POST /api/login controllers.Application.login
GET /api/public controllers.Application.publicApi
GET /api/private controllers.Application.privateApi
GET /api/admin controllers.Application.adminApi

# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
Binary file added examples/play-angular/public/images/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
135 changes: 135 additions & 0 deletions examples/play-angular/public/javascripts/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
angular.module('app', ['ngAnimate'])

.config(['$provide', '$httpProvider', function ($provide, $httpProvider) {
$provide.factory('AuthenticationInterceptor', ['$q', '$rootScope', function ($q, $rootScope) {
return {
'request': function (config) {
config.headers['Authorization'] = JWT.get();
return config;
},

'responseError': function (rejection) {
console.log(rejection);

if (rejection.status === 401) {
// User isn't authenticated
$rootScope.$emit('notification', 'warning', 'You need to be authenticated to access such API.')
} else if (rejection.status === 403) {
// User is authenticated but do not have permission to access this API
$rootScope.$emit('notification', 'warning', 'Sorry, you do not have access to this API... Maybe if your username was "admin"... who knows...')
}

return $q.reject(rejection);
}
}
}]);

$httpProvider.interceptors.push('AuthenticationInterceptor');
}])

.run(['$rootScope', 'Authenticated', function ($rootScope, Authenticated) {
$rootScope.$on('notification', function (e, severity, message) {
$rootScope.notification = {severity: severity, message: message};
});

$rootScope.closeNotification = function closeNotification() {
$rootScope.notification = null;
};

$rootScope.user = Authenticated.current
}])

.factory('Authenticated', ['$http', '$rootScope', function ($http, $rootScope) {
var user = null;
sync();

function sync() {
var session = JWT.remember();
user = session && session.claim && session.claim.user;
$rootScope.authenticated = !!user;
}

function login (data) {
return $http.post('/api/login', data).then(function (response) {
var token = response.headers("Authorization");
var session = JWT.read(token);

console.log(token);
console.log(session);

if (JWT.validate(session)) {
console.log('valid');
JWT.keep(session);
sync();
} else {
logout();
}
});
}

function logout() {
JWT.forget();
sync();
}

function isAuthenticated() {
return !!user;
}

function current() {
return user;
}

return {
login: login,
logout: logout,
isAuthenticated: isAuthenticated,
current: current
}
}])

.controller('HomeCtrl', ['$scope', '$http', 'Authenticated', function ($scope, $http, Authenticated) {
var ctrl = this;
ctrl.notification = null;

ctrl.test = "test";

ctrl.loginForm = {};

ctrl.login = function login() {
console.log('login', ctrl.loginForm);
Authenticated.login(ctrl.loginForm).then(function () {
ctrl.loginForm = {};
}, function (error) {
ctrl.notif('error', 'Invalid username or password!');
});
};

ctrl.logout = function logout() {
Authenticated.logout();
};

function get(endpoint) {
return $http.get(endpoint).then(function (response) {
ctrl.notif('success', response.data);
}, function (error) {
// 401 and 403 errors are already handled by the interceptor
});
}

ctrl.publicCall = function publicCall() {
get('/api/public');
};

ctrl.privateCall = function privateCall() {
get('/api/private');
};

ctrl.adminCall = function privateCall() {
get('/api/admin');
};

ctrl.notif = function notif(severity, message) {
$scope.$emit('notification', severity, message);
};
}]);
Loading

0 comments on commit 155b4cd

Please sign in to comment.