keycloak-uncached

Changes

forms/pom.xml 53(+9 -44)

forms/src/main/java/org/keycloak/forms/model/Property.java 21(+0 -21)

forms/src/main/java/org/keycloak/forms/model/RequiredCredential.java 27(+0 -27)

forms/src/main/java/org/keycloak/forms/model/SocialProvider.java 27(+0 -27)

forms/src/main/java/org/keycloak/service/FormServiceImpl.java 207(+0 -207)

forms/src/main/resources/META-INF/resources/forms/theme/default/css/zocial/zocial.less 281(+0 -281)

forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl 84(+0 -84)

forms/src/main/resources/META-INF/services/org.keycloak.services.FormService 1(+0 -1)

server/pom.xml 27(+26 -1)

services/pom.xml 16(+16 -0)

services/src/main/java/org/keycloak/services/FormService.java 210(+0 -210)

services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java 267(+0 -267)

services/src/main/java/org/keycloak/services/resources/flows/Pages.java 61(+0 -61)

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/index.html b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
index 529fe85..c85ab9b 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/index.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
@@ -3,7 +3,7 @@
 
 <head>
     <meta charset="utf-8">
-    <title>Keycloak</title>
+    <title>Keycloak Admin Console</title>
 
     <link rel="icon" href="/auth/admin-ui/img/favicon.ico">
 
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index aa32945..bda811f 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -47,6 +47,9 @@ module.config([ '$routeProvider', function($routeProvider) {
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
                 }
             },
             controller : 'RealmDetailCtrl'
@@ -78,6 +81,9 @@ module.config([ '$routeProvider', function($routeProvider) {
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
                 }
             },
             controller : 'RealmSocialCtrl'
@@ -780,4 +786,10 @@ module.filter('remove', function() {
 
         return out;
     };
+});
+
+module.filter('capitalize', function() {
+    return function(input) {
+        return input.substring(0, 1).toUpperCase() + input.substring(1);
+    }
 });
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 3ff8682..31f5834 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -141,8 +141,9 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
 });
 
 
-module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications) {
+module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
     $scope.createRealm = !realm.realm;
+    $scope.serverInfo = serverInfo;
 
     console.log('RealmDetailCtrl');
 
@@ -259,14 +260,7 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $ht
 module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {
     console.log('RealmRequiredCredentialsCtrl');
 
-    $scope.realm = {
-        id : realm.realm, realm : realm.realm, social : realm.social,
-        requiredCredentials : realm.requiredCredentials,
-        requiredApplicationCredentials : realm.requiredApplicationCredentials,
-        requiredOAuthClientCredentials : realm.requiredOAuthClientCredentials,
-        registrationAllowed : realm.registrationAllowed,
-        passwordPolicy: realm.passwordPolicy
-    };
+    $scope.realm = realm;
 
     var oldCopy = angular.copy($scope.realm);
 
@@ -274,8 +268,12 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
     $scope.policyMessages = PasswordPolicy.policyMessages;
 
     $scope.policy = PasswordPolicy.parse(realm.passwordPolicy);
+    var oldPolicy = angular.copy($scope.policy);
 
     $scope.addPolicy = function(policy){
+        if (!$scope.policy) {
+            $scope.policy = [];
+        }
         $scope.policy.push(policy);
     }
 
@@ -298,7 +296,7 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
     }, true);
 
     $scope.$watch('policy', function(oldVal, newVal) {
-        if (oldVal != newVal) {
+        if (!angular.equals($scope.policy, oldPolicy)) {
             $scope.realm.passwordPolicy = PasswordPolicy.toString($scope.policy);
             $scope.changed = true;
         }
@@ -311,14 +309,13 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
             $location.url("/realms/" + realm.realm + "/required-credentials");
             Notifications.success("Your changes have been saved to the realm.");
             oldCopy = angular.copy($scope.realm);
+            oldPolicy = angular.copy($scope.policy);
         });
     };
 
     $scope.reset = function() {
         $scope.realm = angular.copy(oldCopy);
-        $scope.policy = PasswordPolicy.parse(oldCopy.passwordPolicy);
-        console.debug(realm.passwordPolicy);
-
+        $scope.policy = angular.copy(oldPolicy);
         $scope.changed = false;
     };
 });
@@ -473,97 +470,41 @@ module.controller('RealmDefaultRolesCtrl', function ($scope, Realm, realm, appli
 
 });
 
-module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, Notifications) {
+module.controller('RealmSocialCtrl', function($scope, realm, Realm, serverInfo, $location, Notifications) {
     console.log('RealmSocialCtrl');
 
-    $scope.realm = { id : realm.id, realm : realm.realm, social : realm.social, registrationAllowed : realm.registrationAllowed,
-        tokenLifespan : realm.tokenLifespan,  accessCodeLifespan : realm.accessCodeLifespan };
-
-    if (!realm["socialProviders"]){
-        $scope.realm["socialProviders"] = {};
-    } else {
-        $scope.realm["socialProviders"] = realm.socialProviders;
-    }
+    $scope.realm = angular.copy(realm);
+    $scope.serverInfo = serverInfo;
 
-    // Hardcoded provider list in form of map providerId:providerName
-    $scope.allProviders = { google:"Google", facebook:"Facebook", twitter:"Twitter" };
-    $scope.availableProviders = [];
+    $scope.allProviders = serverInfo.socialProviders;
+    $scope.configuredProviders = [];
 
-    for (var provider in $scope.allProviders){
-        $scope.availableProviders.push(provider);
-    }
+    $scope.$watch('realm.socialProviders', function(socialProviders) {
+        $scope.configuredProviders = [];
+         for (var providerConfig in socialProviders) {
+             var i = providerConfig.split('.');
+             if (i.length == 2 && i[1] == 'key') {
+                 $scope.configuredProviders.push(i[0]);
+             }
+         }
+    }, true);
 
     var oldCopy = angular.copy($scope.realm);
     $scope.changed = false;
     $scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/rest/social/callback");
 
-    // To get rid of the "undefined" option in the provider select list
-    // Setting the 1st option from the list (if the list is not empty)
-    var selectFirstProvider = function(){
-        if ($scope.unsetProviders.length > 0){
-            $scope.newProviderId = $scope.unsetProviders[0];
-        } else {
-            $scope.newProviderId = null;
+    $scope.addProvider = function(pId) {
+        if (!$scope.realm.socialProviders) {
+            $scope.realm.socialProviders = {};
         }
-    }
-
-    // Fill in configured providers
-    var initSocial = function() {
-        // postSaveProviders is used for remembering providers which were already validated after pressing the save button
-        // thanks to this it's easy to distinguish between newly added fields and those already tried to be saved
-        $scope.postSaveProviders = [];
-        $scope.unsetProviders = [];
-        $scope.configuredProviders = [];
 
-        for (var providerConfig in $scope.realm.socialProviders){
-            // Get the provider ID which is before the '.' (i.e. google in google.key or google.secret)
-            if ($scope.realm.socialProviders.hasOwnProperty(providerConfig)){
-                var pId = providerConfig.split('.')[0];
-                if ($scope.configuredProviders.indexOf(pId) < 0){
-                    $scope.configuredProviders.push(pId);
-                }
-            }
-        }
-
-        // If no providers are already configured, you can add any of them
-        if ($scope.configuredProviders.length == 0){
-            $scope.unsetProviders = $scope.availableProviders.slice(0);
-        } else {
-            for (var i = 0; i < $scope.availableProviders.length; i++){
-                var providerId = $scope.availableProviders[i];
-                if ($scope.configuredProviders.indexOf(providerId) < 0){
-                    $scope.unsetProviders.push(providerId);
-                }
-            }
-        }
-
-        selectFirstProvider();
-    };
-
-    initSocial();
-
-    $scope.addProvider = function() {
-        if ($scope.availableProviders.indexOf($scope.newProviderId) > -1){
-            $scope.realm.socialProviders[$scope.newProviderId+".key"]="";
-            $scope.realm.socialProviders[$scope.newProviderId+".secret"]="";
-            $scope.configuredProviders.push($scope.newProviderId);
-            $scope.unsetProviders.splice($scope.unsetProviders.indexOf($scope.newProviderId),1);
-            selectFirstProvider();
-        }
+        $scope.realm.socialProviders[pId + ".key"] = "";
+        $scope.realm.socialProviders[pId + ".secret"] = "";
     };
 
     $scope.removeProvider = function(pId) {
         delete $scope.realm.socialProviders[pId+".key"];
         delete $scope.realm.socialProviders[pId+".secret"];
-        $scope.configuredProviders.splice($scope.configuredProviders.indexOf(pId),1);
-
-        // Removing from postSaveProviders, so the empty fields are not red if the provider is added to the list again
-        var rId = $scope.postSaveProviders.indexOf(pId);
-        if (rId > -1){
-            $scope.postSaveProviders.splice(rId,1)
-        }
-
-        $scope.unsetProviders.push(pId);
     };
 
     $scope.$watch('realm', function() {
@@ -586,8 +527,6 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
     $scope.reset = function() {
         $scope.realm = angular.copy(oldCopy);
         $scope.changed = false;
-        // Initialize lists of configured and unset providers again
-        initSocial();
     };
 
 });
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js
index d22e03b..faf05cd 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/loaders.js
@@ -35,6 +35,10 @@ module.factory('RealmListLoader', function(Loader, Realm, $q) {
 	return Loader.get(Realm);
 });
 
+module.factory('ServerInfoLoader', function(Loader, ServerInfo, $q) {
+    return Loader.get(ServerInfo);
+});
+
 module.factory('RealmLoader', function(Loader, Realm, $route, $q) {
 	return Loader.get(Realm, function() {
 		return {
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
index 33e47a9..b572a10 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
@@ -1,645 +1,649 @@
-'use strict';
-
-var module = angular.module('keycloak.services', [ 'ngResource' ]);
-
-module.service('Dialog', function($dialog) {
-	var dialog = {};
-
-	var escapeHtml = function(str) {
-		var div = document.createElement('div');
-		div.appendChild(document.createTextNode(str));
-		return div.innerHTML;
-	};
-
-	dialog.confirmDelete = function(name, type, success) {
-		var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
-		var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' "' + escapeHtml(name) + '"?</span>' +
-            '<span>This action can\'t be undone.</span>';
-		var btns = [ {
-			result : 'cancel',
-			label : 'Cancel'
-		}, {
-			result : 'ok',
-			label : 'Delete',
-			cssClass : 'destructive'
-		} ];
-
-		$dialog.messageBox(title, msg, btns).open().then(function(result) {
-			if (result == "ok") {
-				success();
-			}
-		});
-	}
-
-    dialog.confirmGenerateKeys = function(name, type, success) {
-        var title = 'Generate new keys for realm';
-        var msg = '<span class="primary">Are you sure you want to permanently generate new keys for ' + name + '"?</span>' +
-            '<span>This action can\'t be undone.</span>';
-        var btns = [ {
-            result : 'cancel',
-            label : 'Cancel'
-        }, {
-            result : 'ok',
-            label : 'Generate new keys',
-            cssClass : 'destructive'
-        } ];
-
-        $dialog.messageBox(title, msg, btns).open().then(function(result) {
-            if (result == "ok") {
-                success();
-            }
-        });
-    }
-
-	return dialog
-});
-
-module.factory('Notifications', function($rootScope, $timeout) {
-	// time (in ms) the notifications are shown
-	var delay = 5000;
-
-	var notifications = {};
-
-	var scheduled = null;
-	var schedulePop = function() {
-		if (scheduled) {
-			$timeout.cancel(scheduled);
-		}
-
-		scheduled = $timeout(function() {
-			$rootScope.notification = null;
-			scheduled = null;
-		}, delay);
-	};
-
-	if (!$rootScope.notifications) {
-		$rootScope.notifications = [];
-	}
-
-	notifications.message = function(type, header, message) {
-		$rootScope.notification = {
-			type : type,
-			header: header,
-			message : message
-		};
-
-		schedulePop();
-	}
-
-	notifications.info = function(message) {
-		notifications.message("info", "Info!", message);
-	};
-
-	notifications.success = function(message) {
-		notifications.message("success", "Success!", message);
-	};
-
-	notifications.error = function(message) {
-		notifications.message("error", "Error!", message);
-	};
-
-	notifications.warn = function(message) {
-		notifications.message("warn", "Warning!", message);
-	};
-
-	return notifications;
-});
-
-module.factory('Realm', function($resource) {
-	return $resource('/auth/rest/admin/realms/:id', {
-		id : '@realm'
-	}, {
-		update : {
-			method : 'PUT'
-		},
-        create : {
-            method : 'POST',
-            params : { id : ''}
-        }
-
-    });
-});
-
-module.factory('User', function($resource) {
-	return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
-		realm : '@realm',
-		userId : '@userId'
-	}, {
-        update : {
-            method : 'PUT'
-        }
-	});
-});
-
-module.factory('UserCredentials', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/users/:userId/credentials', {
-        realm : '@realm',
-        userId : '@userId'
-    }, {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('RealmRoleMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/realm', {
-        realm : '@realm',
-        userId : '@userId'
-    });
-});
-
-module.factory('ApplicationRoleMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/applications/:application', {
-        realm : '@realm',
-        userId : '@userId',
-        application : "@application"
-    });
-});
-
-module.factory('ApplicationRealmScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/realm', {
-        realm : '@realm',
-        application : '@application'
-    });
-});
-
-module.factory('ApplicationApplicationScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/applications/:targetApp', {
-        realm : '@realm',
-        application : '@application',
-        targetApp : '@targetApp'
-    });
-});
-
-
-
-module.factory('RealmRoles', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles', {
-        realm : '@realm'
-    });
-});
-
-module.factory('RoleRealmComposites', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/realm', {
-        realm : '@realm',
-        role : '@role'
-    });
-});
-
-module.factory('RoleApplicationComposites', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
-        realm : '@realm',
-        role : '@role',
-        application : "@application"
-    });
-});
-
-
-function roleControl($scope, realm, role, roles, applications,
-                     ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
-                     $http, $location, Notifications, Dialog) {
-
-    $scope.$watch(function () {
-        return $location.path();
-    }, function () {
-        $scope.path = $location.path().substring(1).split("/");
-    });
-
-    $scope.$watch('role', function () {
-        if (!angular.equals($scope.role, role)) {
-            $scope.changed = true;
-        }
-    }, true);
-
-    $scope.update = function () {
-        RoleById.update({
-            realm: realm.realm,
-            role: role.id
-        }, $scope.role, function () {
-            $scope.changed = false;
-            role = angular.copy($scope.role);
-            Notifications.success("Your changes have been saved to the role.");
-        });
-    };
-
-    $scope.reset = function () {
-        $scope.role = angular.copy(role);
-        $scope.changed = false;
-    };
-
-    if (!role.id) return;
-
-    $scope.realmRoles = angular.copy(roles);
-    $scope.selectedRealmRoles = [];
-    $scope.selectedRealmMappings = [];
-    $scope.realmMappings = [];
-    $scope.applications = applications;
-    $scope.applicationRoles = [];
-    $scope.selectedApplicationRoles = [];
-    $scope.selectedApplicationMappings = [];
-    $scope.applicationMappings = [];
-
-    console.log('remove self');
-    for (var j = 0; j < $scope.realmRoles.length; j++) {
-        if ($scope.realmRoles[j].id == role.id) {
-            var realmRole = $scope.realmRoles[j];
-            var idx = $scope.realmRoles.indexOf(realmRole);
-            $scope.realmRoles.splice(idx, 1);
-            break;
-        }
-    }
-
-
-    $scope.realmMappings = RoleRealmComposites.query({realm : realm.realm, role : role.id}, function(){
-        for (var i = 0; i < $scope.realmMappings.length; i++) {
-            var role = $scope.realmMappings[i];
-            for (var j = 0; j < $scope.realmRoles.length; j++) {
-                var realmRole = $scope.realmRoles[j];
-                if (realmRole.id == role.id) {
-                    var idx = $scope.realmRoles.indexOf(realmRole);
-                    if (idx != -1) {
-                        $scope.realmRoles.splice(idx, 1);
-                        break;
-                    }
-                }
-            }
-        }
-    });
-
-    $scope.addRealmRole = function() {
-        $http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
-                $scope.selectedRealmRoles).success(function() {
-                for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
-                    var role = $scope.selectedRealmRoles[i];
-                    var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
-                    if (idx != -1) {
-                        $scope.realmRoles.splice(idx, 1);
-                        $scope.realmMappings.push(role);
-                    }
-                }
-                $scope.selectRealmRoles = [];
-            });
-    };
-
-    $scope.deleteRealmRole = function() {
-        $http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
-            {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
-                for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
-                    var role = $scope.selectedRealmMappings[i];
-                    var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
-                    if (idx != -1) {
-                        $scope.realmMappings.splice(idx, 1);
-                        $scope.realmRoles.push(role);
-                    }
-                }
-                $scope.selectedRealmMappings = [];
-            });
-    };
-
-    $scope.addApplicationRole = function() {
-        $http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
-                $scope.selectedApplicationRoles).success(function() {
-                for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
-                    var role = $scope.selectedApplicationRoles[i];
-                    var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
-                    if (idx != -1) {
-                        $scope.applicationRoles.splice(idx, 1);
-                        $scope.applicationMappings.push(role);
-                    }
-                }
-                $scope.selectedApplicationRoles = [];
-            });
-    };
-
-    $scope.deleteApplicationRole = function() {
-        $http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
-            {data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
-                for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
-                    var role = $scope.selectedApplicationMappings[i];
-                    var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
-                    if (idx != -1) {
-                        $scope.applicationMappings.splice(idx, 1);
-                        $scope.applicationRoles.push(role);
-                    }
-                }
-                $scope.selectedApplicationMappings = [];
-            });
-    };
-
-
-    $scope.changeApplication = function() {
-        $scope.applicationRoles = ApplicationRole.query({realm : realm.realm, application : $scope.compositeApp.name}, function() {
-                $scope.applicationMappings = RoleApplicationComposites.query({realm : realm.realm, role : role.id, application : $scope.compositeApp.name}, function(){
-                    for (var i = 0; i < $scope.applicationMappings.length; i++) {
-                        var role = $scope.applicationMappings[i];
-                        for (var j = 0; j < $scope.applicationRoles.length; j++) {
-                            var realmRole = $scope.applicationRoles[j];
-                            if (realmRole.id == role.id) {
-                                var idx = $scope.applicationRoles.indexOf(realmRole);
-                                if (idx != -1) {
-                                    $scope.applicationRoles.splice(idx, 1);
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                });
-                for (var j = 0; j < $scope.applicationRoles.length; j++) {
-                    if ($scope.applicationRoles[j] == role.id) {
-                        var appRole = $scope.applicationRoles[j];
-                        var idx = $scope.applicationRoles.indexof(appRole);
-                        $scope.applicationRoles.splice(idx, 1);
-                        break;
-                    }
-                }
-            }
-        );
-    };
-
-
-
-
-}
-
-
-module.factory('Role', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles/:role', {
-        realm : '@realm',
-        role : '@role'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('RoleById', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role', {
-        realm : '@realm',
-        role : '@role'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('ApplicationRole', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/roles/:role', {
-        realm : '@realm',
-        application : "@application",
-        role : '@role'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-
-module.factory('Application', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application', {
-        realm : '@realm',
-        application : '@name'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('ApplicationInstallation', function($resource) {
-    var url = '/auth/rest/admin/realms/:realm/applications/:application/installation';
-    var resource = $resource('/auth/rest/admin/realms/:realm/applications/:application/installation', {
-        realm : '@realm',
-        application : '@application'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-    resource.url = function(parameters) {
-        return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
-    }
-    return resource;
-});
-
-module.factory('ApplicationCredentials', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/credentials', {
-        realm : '@realm',
-        application : '@application'
-    },  {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('ApplicationOrigins', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/allowed-origins', {
-        realm : '@realm',
-        application : '@application'
-    },  {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('OAuthClient', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:id', {
-        realm : '@realm',
-        id : '@id'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('OAuthClientCredentials', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/credentials', {
-        realm : '@realm',
-        oauth : '@oauth'
-    },  {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('OAuthClientRealmScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/realm', {
-        realm : '@realm',
-        oauth : '@oauth'
-    });
-});
-
-module.factory('OAuthClientApplicationScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/applications/:targetApp', {
-        realm : '@realm',
-        oauth : '@oauth',
-        targetApp : '@targetApp'
-    });
-});
-
-module.factory('OAuthClientInstallation', function($resource) {
-    var url = '/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation';
-    var resource = $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation', {
-        realm : '@realm',
-        oauth : '@oauth'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-    resource.url = function(parameters) {
-        return url.replace(':realm', parameters.realm).replace(':oauth', parameters.oauth);
-    }
-    return resource;
-});
-
-
-module.factory('Current', function(Realm, $route) {
-    var current = {};
-
-    current.realms = {};
-    current.realm = null;
-    current.applications = {};
-    current.application = null;
-
-    current.refresh = function() {
-        current.realm = null;
-        current.realms = Realm.query(null, function(realms) {
-            if ($route.current.params.realm) {
-                for (var i = 0; i < realms.length; i++) {
-                    if (realms[i].realm == $route.current.params.realm) {
-                        current.realm =  realms[i];
-                    }
-                }
-            }
-        });
-    }
-
-    current.refresh();
-
-    return current;
-});
-
-module.factory('TimeUnit', function() {
-    var t = {};
-
-    t.autoUnit = function(time) {
-        var unit = 'Seconds';
-        if (time % 60 == 0) {
-            unit = 'Minutes';
-            time  = time / 60;
-        }
-        if (time % 60 == 0) {
-            unit = 'Hours';
-            time = time / 60;
-        }
-        if (time % 24 == 0) {
-            unit = 'Days'
-            time = time / 24;
-        }
-        return unit;
-    }
-
-    t.toSeconds = function(time, unit) {
-        switch (unit) {
-            case 'Seconds': return time;
-            case 'Minutes': return time * 60;
-            case 'Hours': return time * 360;
-            case 'Days': return time * 86400;
-            default: throw 'invalid unit ' + unit;
-        }
-    }
-
-    t.toUnit = function(time, unit) {
-        switch (unit) {
-            case 'Seconds': return time;
-            case 'Minutes': return Math.ceil(time / 60);
-            case 'Hours': return Math.ceil(time / 360);
-            case 'Days': return Math.ceil(time / 86400);
-            default: throw 'invalid unit ' + unit;
-        }
-    }
-
-    t.convert = function(time, from, to) {
-        var seconds = t.toSeconds(time, from);
-        return t.toUnit(seconds, to);
-    }
-
-    return t;
-});
-
-
-module.factory('PasswordPolicy', function() {
-    var p = {};
-
-    p.policyMessages = {
-        length:         "Minimal password length (integer type). Default value is 8.",
-        digits:         "Minimal number (integer type) of digits in password. Default value is 1.",
-        lowerCase:      "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
-        upperCase:      "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
-        specialChars:   "Minimal number (integer type) of special characters in password. Default value is 1."
-    }
-
-    p.allPolicies = [
-        { name: 'length', value: 8 },
-        { name: 'digits', value: 1 },
-        { name: 'lowerCase', value: 1 },
-        { name: 'upperCase', value: 1 },
-        { name: 'specialChars', value: 1 }
-    ];
-
-    p.parse = function(policyString) {
-        var policies = [];
-
-        if (!policyString || policyString.length == 0){
-            return policies;
-        }
-
-        var policyArray = policyString.split(" and ");
-
-        for (var i = 0; i < policyArray.length; i ++){
-            var policyToken = policyArray[i];
-            var re = /(\w+)\(*(\d*)\)*/;
-
-            var policyEntry = re.exec(policyToken);
-
-            policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
-
-        }
-
-        return policies;
-    };
-
-    p.toString = function(policies) {
-        if (!policies || policies.length == 0) {
-            return null;
-        }
-
-        var policyString = "";
-
-        for (var i in policies){
-            policyString += policies[i].name;
-            if ( policies[i].value ){
-                policyString += '(' + policies[i].value + ')';
-            }
-            policyString += " and ";
-        }
-
-        policyString = policyString.substring(0, policyString.length - 5);
-
-        return policyString;
-    };
-
-    return p;
+'use strict';
+
+var module = angular.module('keycloak.services', [ 'ngResource' ]);
+
+module.service('Dialog', function($dialog) {
+	var dialog = {};
+
+	var escapeHtml = function(str) {
+		var div = document.createElement('div');
+		div.appendChild(document.createTextNode(str));
+		return div.innerHTML;
+	};
+
+	dialog.confirmDelete = function(name, type, success) {
+		var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
+		var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' "' + escapeHtml(name) + '"?</span>' +
+            '<span>This action can\'t be undone.</span>';
+		var btns = [ {
+			result : 'cancel',
+			label : 'Cancel'
+		}, {
+			result : 'ok',
+			label : 'Delete',
+			cssClass : 'destructive'
+		} ];
+
+		$dialog.messageBox(title, msg, btns).open().then(function(result) {
+			if (result == "ok") {
+				success();
+			}
+		});
+	}
+
+    dialog.confirmGenerateKeys = function(name, type, success) {
+        var title = 'Generate new keys for realm';
+        var msg = '<span class="primary">Are you sure you want to permanently generate new keys for ' + name + '"?</span>' +
+            '<span>This action can\'t be undone.</span>';
+        var btns = [ {
+            result : 'cancel',
+            label : 'Cancel'
+        }, {
+            result : 'ok',
+            label : 'Generate new keys',
+            cssClass : 'destructive'
+        } ];
+
+        $dialog.messageBox(title, msg, btns).open().then(function(result) {
+            if (result == "ok") {
+                success();
+            }
+        });
+    }
+
+	return dialog
+});
+
+module.factory('Notifications', function($rootScope, $timeout) {
+	// time (in ms) the notifications are shown
+	var delay = 5000;
+
+	var notifications = {};
+
+	var scheduled = null;
+	var schedulePop = function() {
+		if (scheduled) {
+			$timeout.cancel(scheduled);
+		}
+
+		scheduled = $timeout(function() {
+			$rootScope.notification = null;
+			scheduled = null;
+		}, delay);
+	};
+
+	if (!$rootScope.notifications) {
+		$rootScope.notifications = [];
+	}
+
+	notifications.message = function(type, header, message) {
+		$rootScope.notification = {
+			type : type,
+			header: header,
+			message : message
+		};
+
+		schedulePop();
+	}
+
+	notifications.info = function(message) {
+		notifications.message("info", "Info!", message);
+	};
+
+	notifications.success = function(message) {
+		notifications.message("success", "Success!", message);
+	};
+
+	notifications.error = function(message) {
+		notifications.message("error", "Error!", message);
+	};
+
+	notifications.warn = function(message) {
+		notifications.message("warn", "Warning!", message);
+	};
+
+	return notifications;
+});
+
+module.factory('Realm', function($resource) {
+	return $resource('/auth/rest/admin/realms/:id', {
+		id : '@realm'
+	}, {
+		update : {
+			method : 'PUT'
+		},
+        create : {
+            method : 'POST',
+            params : { id : ''}
+        }
+
+    });
+});
+
+module.factory('ServerInfo', function($resource) {
+    return $resource('/auth/rest/admin/serverinfo');
+});
+
+module.factory('User', function($resource) {
+	return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
+		realm : '@realm',
+		userId : '@userId'
+	}, {
+        update : {
+            method : 'PUT'
+        }
+	});
+});
+
+module.factory('UserCredentials', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/users/:userId/credentials', {
+        realm : '@realm',
+        userId : '@userId'
+    }, {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('RealmRoleMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/realm', {
+        realm : '@realm',
+        userId : '@userId'
+    });
+});
+
+module.factory('ApplicationRoleMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/applications/:application', {
+        realm : '@realm',
+        userId : '@userId',
+        application : "@application"
+    });
+});
+
+module.factory('ApplicationRealmScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/realm', {
+        realm : '@realm',
+        application : '@application'
+    });
+});
+
+module.factory('ApplicationApplicationScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/applications/:targetApp', {
+        realm : '@realm',
+        application : '@application',
+        targetApp : '@targetApp'
+    });
+});
+
+
+
+module.factory('RealmRoles', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles', {
+        realm : '@realm'
+    });
+});
+
+module.factory('RoleRealmComposites', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/realm', {
+        realm : '@realm',
+        role : '@role'
+    });
+});
+
+module.factory('RoleApplicationComposites', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
+        realm : '@realm',
+        role : '@role',
+        application : "@application"
+    });
+});
+
+
+function roleControl($scope, realm, role, roles, applications,
+                     ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
+                     $http, $location, Notifications, Dialog) {
+
+    $scope.$watch(function () {
+        return $location.path();
+    }, function () {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+
+    $scope.$watch('role', function () {
+        if (!angular.equals($scope.role, role)) {
+            $scope.changed = true;
+        }
+    }, true);
+
+    $scope.update = function () {
+        RoleById.update({
+            realm: realm.realm,
+            role: role.id
+        }, $scope.role, function () {
+            $scope.changed = false;
+            role = angular.copy($scope.role);
+            Notifications.success("Your changes have been saved to the role.");
+        });
+    };
+
+    $scope.reset = function () {
+        $scope.role = angular.copy(role);
+        $scope.changed = false;
+    };
+
+    if (!role.id) return;
+
+    $scope.realmRoles = angular.copy(roles);
+    $scope.selectedRealmRoles = [];
+    $scope.selectedRealmMappings = [];
+    $scope.realmMappings = [];
+    $scope.applications = applications;
+    $scope.applicationRoles = [];
+    $scope.selectedApplicationRoles = [];
+    $scope.selectedApplicationMappings = [];
+    $scope.applicationMappings = [];
+
+    console.log('remove self');
+    for (var j = 0; j < $scope.realmRoles.length; j++) {
+        if ($scope.realmRoles[j].id == role.id) {
+            var realmRole = $scope.realmRoles[j];
+            var idx = $scope.realmRoles.indexOf(realmRole);
+            $scope.realmRoles.splice(idx, 1);
+            break;
+        }
+    }
+
+
+    $scope.realmMappings = RoleRealmComposites.query({realm : realm.realm, role : role.id}, function(){
+        for (var i = 0; i < $scope.realmMappings.length; i++) {
+            var role = $scope.realmMappings[i];
+            for (var j = 0; j < $scope.realmRoles.length; j++) {
+                var realmRole = $scope.realmRoles[j];
+                if (realmRole.id == role.id) {
+                    var idx = $scope.realmRoles.indexOf(realmRole);
+                    if (idx != -1) {
+                        $scope.realmRoles.splice(idx, 1);
+                        break;
+                    }
+                }
+            }
+        }
+    });
+
+    $scope.addRealmRole = function() {
+        $http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+                $scope.selectedRealmRoles).success(function() {
+                for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
+                    var role = $scope.selectedRealmRoles[i];
+                    var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
+                    if (idx != -1) {
+                        $scope.realmRoles.splice(idx, 1);
+                        $scope.realmMappings.push(role);
+                    }
+                }
+                $scope.selectRealmRoles = [];
+            });
+    };
+
+    $scope.deleteRealmRole = function() {
+        $http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+            {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
+                for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
+                    var role = $scope.selectedRealmMappings[i];
+                    var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
+                    if (idx != -1) {
+                        $scope.realmMappings.splice(idx, 1);
+                        $scope.realmRoles.push(role);
+                    }
+                }
+                $scope.selectedRealmMappings = [];
+            });
+    };
+
+    $scope.addApplicationRole = function() {
+        $http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+                $scope.selectedApplicationRoles).success(function() {
+                for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
+                    var role = $scope.selectedApplicationRoles[i];
+                    var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
+                    if (idx != -1) {
+                        $scope.applicationRoles.splice(idx, 1);
+                        $scope.applicationMappings.push(role);
+                    }
+                }
+                $scope.selectedApplicationRoles = [];
+            });
+    };
+
+    $scope.deleteApplicationRole = function() {
+        $http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+            {data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
+                for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
+                    var role = $scope.selectedApplicationMappings[i];
+                    var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
+                    if (idx != -1) {
+                        $scope.applicationMappings.splice(idx, 1);
+                        $scope.applicationRoles.push(role);
+                    }
+                }
+                $scope.selectedApplicationMappings = [];
+            });
+    };
+
+
+    $scope.changeApplication = function() {
+        $scope.applicationRoles = ApplicationRole.query({realm : realm.realm, application : $scope.compositeApp.name}, function() {
+                $scope.applicationMappings = RoleApplicationComposites.query({realm : realm.realm, role : role.id, application : $scope.compositeApp.name}, function(){
+                    for (var i = 0; i < $scope.applicationMappings.length; i++) {
+                        var role = $scope.applicationMappings[i];
+                        for (var j = 0; j < $scope.applicationRoles.length; j++) {
+                            var realmRole = $scope.applicationRoles[j];
+                            if (realmRole.id == role.id) {
+                                var idx = $scope.applicationRoles.indexOf(realmRole);
+                                if (idx != -1) {
+                                    $scope.applicationRoles.splice(idx, 1);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                });
+                for (var j = 0; j < $scope.applicationRoles.length; j++) {
+                    if ($scope.applicationRoles[j] == role.id) {
+                        var appRole = $scope.applicationRoles[j];
+                        var idx = $scope.applicationRoles.indexof(appRole);
+                        $scope.applicationRoles.splice(idx, 1);
+                        break;
+                    }
+                }
+            }
+        );
+    };
+
+
+
+
+}
+
+
+module.factory('Role', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles/:role', {
+        realm : '@realm',
+        role : '@role'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('RoleById', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role', {
+        realm : '@realm',
+        role : '@role'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('ApplicationRole', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/roles/:role', {
+        realm : '@realm',
+        application : "@application",
+        role : '@role'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+
+module.factory('Application', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application', {
+        realm : '@realm',
+        application : '@name'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('ApplicationInstallation', function($resource) {
+    var url = '/auth/rest/admin/realms/:realm/applications/:application/installation';
+    var resource = $resource('/auth/rest/admin/realms/:realm/applications/:application/installation', {
+        realm : '@realm',
+        application : '@application'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+    resource.url = function(parameters) {
+        return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
+    }
+    return resource;
+});
+
+module.factory('ApplicationCredentials', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/credentials', {
+        realm : '@realm',
+        application : '@application'
+    },  {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('ApplicationOrigins', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/allowed-origins', {
+        realm : '@realm',
+        application : '@application'
+    },  {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('OAuthClient', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:id', {
+        realm : '@realm',
+        id : '@id'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('OAuthClientCredentials', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/credentials', {
+        realm : '@realm',
+        oauth : '@oauth'
+    },  {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('OAuthClientRealmScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/realm', {
+        realm : '@realm',
+        oauth : '@oauth'
+    });
+});
+
+module.factory('OAuthClientApplicationScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/applications/:targetApp', {
+        realm : '@realm',
+        oauth : '@oauth',
+        targetApp : '@targetApp'
+    });
+});
+
+module.factory('OAuthClientInstallation', function($resource) {
+    var url = '/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation';
+    var resource = $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation', {
+        realm : '@realm',
+        oauth : '@oauth'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+    resource.url = function(parameters) {
+        return url.replace(':realm', parameters.realm).replace(':oauth', parameters.oauth);
+    }
+    return resource;
+});
+
+
+module.factory('Current', function(Realm, $route) {
+    var current = {};
+
+    current.realms = {};
+    current.realm = null;
+    current.applications = {};
+    current.application = null;
+
+    current.refresh = function() {
+        current.realm = null;
+        current.realms = Realm.query(null, function(realms) {
+            if ($route.current.params.realm) {
+                for (var i = 0; i < realms.length; i++) {
+                    if (realms[i].realm == $route.current.params.realm) {
+                        current.realm =  realms[i];
+                    }
+                }
+            }
+        });
+    }
+
+    current.refresh();
+
+    return current;
+});
+
+module.factory('TimeUnit', function() {
+    var t = {};
+
+    t.autoUnit = function(time) {
+        var unit = 'Seconds';
+        if (time % 60 == 0) {
+            unit = 'Minutes';
+            time  = time / 60;
+        }
+        if (time % 60 == 0) {
+            unit = 'Hours';
+            time = time / 60;
+        }
+        if (time % 24 == 0) {
+            unit = 'Days'
+            time = time / 24;
+        }
+        return unit;
+    }
+
+    t.toSeconds = function(time, unit) {
+        switch (unit) {
+            case 'Seconds': return time;
+            case 'Minutes': return time * 60;
+            case 'Hours': return time * 360;
+            case 'Days': return time * 86400;
+            default: throw 'invalid unit ' + unit;
+        }
+    }
+
+    t.toUnit = function(time, unit) {
+        switch (unit) {
+            case 'Seconds': return time;
+            case 'Minutes': return Math.ceil(time / 60);
+            case 'Hours': return Math.ceil(time / 360);
+            case 'Days': return Math.ceil(time / 86400);
+            default: throw 'invalid unit ' + unit;
+        }
+    }
+
+    t.convert = function(time, from, to) {
+        var seconds = t.toSeconds(time, from);
+        return t.toUnit(seconds, to);
+    }
+
+    return t;
+});
+
+
+module.factory('PasswordPolicy', function() {
+    var p = {};
+
+    p.policyMessages = {
+        length:         "Minimal password length (integer type). Default value is 8.",
+        digits:         "Minimal number (integer type) of digits in password. Default value is 1.",
+        lowerCase:      "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
+        upperCase:      "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
+        specialChars:   "Minimal number (integer type) of special characters in password. Default value is 1."
+    }
+
+    p.allPolicies = [
+        { name: 'length', value: 8 },
+        { name: 'digits', value: 1 },
+        { name: 'lowerCase', value: 1 },
+        { name: 'upperCase', value: 1 },
+        { name: 'specialChars', value: 1 }
+    ];
+
+    p.parse = function(policyString) {
+        var policies = [];
+
+        if (!policyString || policyString.length == 0){
+            return policies;
+        }
+
+        var policyArray = policyString.split(" and ");
+
+        for (var i = 0; i < policyArray.length; i ++){
+            var policyToken = policyArray[i];
+            var re = /(\w+)\(*(\d*)\)*/;
+
+            var policyEntry = re.exec(policyToken);
+
+            policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
+
+        }
+
+        return policies;
+    };
+
+    p.toString = function(policies) {
+        if (!policies || policies.length == 0) {
+            return null;
+        }
+
+        var policyString = "";
+
+        for (var i in policies){
+            policyString += policies[i].name;
+            if ( policies[i].value ){
+                policyString += '(' + policies[i].value + ')';
+            }
+            policyString += " and ";
+        }
+
+        policyString = policyString.substring(0, policyString.length - 5);
+
+        return policyString;
+    };
+
+    return p;
 });
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html
index 2ebeb3e..859aea1 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html
@@ -1,7 +1,7 @@
 <div class="header rcue">
     <div class="navbar utility">
         <div class="navbar-inner clearfix container">
-            <h1><a href="#/"><strong>Keycloak</strong> Central Login</a></h1>
+            <h1><a href="#/"><strong>Keycloak</strong> Admin Console</a></h1>
             <ul class="nav pull-right" data-ng-hide="auth.loggedIn">
                 <li><a href="/auth/rest/admin/login?path={{fragment}}">Login</a></li>
                 <li><a href="/auth/rest/admin/registrations">Register</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
index d9e505c..b8352c0 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
@@ -57,8 +57,8 @@
                                     <div class="actions">
                                         <div class="select-rcue">
                                             <select ng-model="selectedPolicy"
-                                                    ng-options="p.name for p in (allPolicies|remove:policy:'name')"
-                                                    data-ng-change="addPolicy(selectedPolicy); selectedAllPolicies = null">
+                                                    ng-options="(p.name|capitalize) for p in (allPolicies|remove:policy:'name')"
+                                                    data-ng-change="addPolicy(selectedPolicy); selectedPolicy = null">
                                                 <option value="" disabled selected>Add policy...</option>
                                             </select>
                                         </div>
@@ -75,7 +75,7 @@
                             <tr ng-repeat="p in policy">
                                 <td>
                                     <div class="clearfix">
-                                        <input class="input-small disabled" type="text" value="{{p.name}}" readonly>
+                                        <input class="input-small disabled" type="text" value="{{p.name|capitalize}}" readonly>
                                     </div>
                                 </td>
                                 <td>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
index c71a4a6..2b6d4b8 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
@@ -66,6 +66,24 @@
                             <input ng-model="realm.requireSsl" name="requireSsl" id="requireSsl" onoffswitch />
                         </div>
                     </fieldset>
+                    <fieldset>
+                        <legend uncollapsed><span class="text">Optional Settings</span></legend>
+                        <div class="form-group">
+                            <label for="loginTheme">Login Theme</label>
+
+                            <div class="controls">
+                                <select id="loginTheme" name="loginTheme" ng-model="realm.loginTheme" ng-options="t for t in serverInfo.themes.login"></select>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label for="accountTheme">Account Theme</label>
+
+                            <div class="controls">
+                                <select id="accountTheme" name="accountTheme" ng-model="realm.accountTheme" ng-options="t for t in serverInfo.themes.account"></select>
+                            </div>
+                        </div>
+                    </fieldset>
+
 
                     <div class="form-actions" data-ng-show="createRealm">
                         <button type="submit" kc-save class="primary" data-ng-show="changed">Save
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html
index 5a75457..999b6be 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html
@@ -38,11 +38,10 @@
                                         <div class="actions">
                                             <div class="select-rcue">
                                                 <select ng-model="newProviderId"
-                                                        ng-options="p as allProviders[p] for p in unsetProviders"
-                                                        placeholder="Please select"></select>
-                                            </div>
-                                            <div>
-                                                <button ng-click="addProvider()" ng-disabled="">Add Provider</button>
+                                                        ng-options="(p|capitalize) for p in (allProviders|remove:configuredProviders)"
+                                                        data-ng-change="addProvider(newProviderId); newProviderId = null">
+                                                    <option value="" disabled selected>Add provider...</option>
+                                                </select>
                                             </div>
                                         </div>
                                     </th>
@@ -58,7 +57,7 @@
                                 <tr ng-repeat="pId in configuredProviders">
                                     <td>
                                         <div class="clearfix">
-                                            <input class="input-small disabled" type="text" value="{{allProviders[pId]}}" readonly>
+                                            <input class="input-small disabled" type="text" value="{{pId|capitalize}}" readonly>
                                         </div>
                                     </td>
                                     <td>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index b1fc9be..e31b41c 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -41,6 +41,8 @@ public class RealmRepresentation {
     protected List<OAuthClientRepresentation> oauthClients;
     protected Map<String, String> socialProviders;
     protected Map<String, String> smtpServer;
+    protected String loginTheme;
+    protected String accountTheme;
 
     public String getSelf() {
         return self;
@@ -317,4 +319,20 @@ public class RealmRepresentation {
     public void setRoles(RolesRepresentation roles) {
         this.roles = roles;
     }
+
+    public String getLoginTheme() {
+        return loginTheme;
+    }
+
+    public void setLoginTheme(String loginTheme) {
+        this.loginTheme = loginTheme;
+    }
+
+    public String getAccountTheme() {
+        return accountTheme;
+    }
+
+    public void setAccountTheme(String accountTheme) {
+        this.accountTheme = accountTheme;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/util/ProviderLoader.java b/core/src/main/java/org/keycloak/util/ProviderLoader.java
new file mode 100644
index 0000000..3685b4f
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/ProviderLoader.java
@@ -0,0 +1,70 @@
+package org.keycloak.util;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProviderLoader<T> implements Iterable<T> {
+
+    private ServiceLoader<T> serviceLoader;
+
+    public static <T> Iterable<T> load(Class<T> service) {
+        ServiceLoader<T> providers = ServiceLoader.load(service);
+        return new ProviderLoader(providers);
+    }
+
+    private ProviderLoader(ServiceLoader<T> serviceLoader) {
+        this.serviceLoader = serviceLoader;
+    }
+
+    @Override
+    public Iterator iterator() {
+        return new ProviderIterator(serviceLoader.iterator());
+    }
+
+    private static class ProviderIterator<T> implements Iterator<T> {
+
+        private Iterator<T> itr;
+
+        private T next;
+
+        private ProviderIterator(Iterator<T> itr) {
+            this.itr = itr;
+            setNext();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+
+        @Override
+        public T next() {
+            T n = next;
+            setNext();
+            return n;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        private void setNext() {
+            next = null;
+            while (itr.hasNext()) {
+                if (itr.hasNext()) {
+                    T n = itr.next();
+                    if (!System.getProperties().containsKey(n.getClass().getName() + ".disabled")) {
+                        next = n;
+                        return;
+                    }
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/distribution/appliance-dist/assembly.xml b/distribution/appliance-dist/assembly.xml
index fc2cb01..172942c 100755
--- a/distribution/appliance-dist/assembly.xml
+++ b/distribution/appliance-dist/assembly.xml
@@ -36,6 +36,10 @@
             <outputDirectory>keycloak/standalone/deployments</outputDirectory>
         </fileSet>
         <fileSet>
+            <directory>${project.build.directory}/unpacked/examples/themes</directory>
+            <outputDirectory>keycloak/standalone/configuration/themes</outputDirectory>
+        </fileSet>
+        <fileSet>
             <directory>${project.build.directory}/unpacked/adapter</directory>
             <outputDirectory>keycloak</outputDirectory>
         </fileSet>
diff --git a/distribution/examples-docs-zip/build.xml b/distribution/examples-docs-zip/build.xml
index 584c83f..5f7a9a6 100755
--- a/distribution/examples-docs-zip/build.xml
+++ b/distribution/examples-docs-zip/build.xml
@@ -41,5 +41,8 @@
                 <exclude name="**/*.iml"/>
             </fileset>
         </copy>
+        <copy todir="target/examples/themes" overwrite="true">
+            <fileset dir="../../examples/themes"/>
+        </copy>
     </target>
 </project>
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index 3af71a1..37c0796 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -13,6 +13,7 @@
                 <!ENTITY SocialGoogle SYSTEM "modules/social-google.xml">
                 <!ENTITY SocialTwitter SYSTEM "modules/social-twitter.xml">
                 <!ENTITY SocialProviderSPI SYSTEM "modules/social-spi.xml">
+                <!ENTITY Themes SYSTEM "modules/themes.xml">
                 <!ENTITY Email SYSTEM "modules/email.xml">
                 ]>
 
@@ -77,6 +78,8 @@
         &SocialProviderSPI;
     </chapter>
 
+    &Themes;
+
     <chapter>
         <title>Email</title>
         <para>
diff --git a/docbook/reference/en/en-US/modules/social-spi.xml b/docbook/reference/en/en-US/modules/social-spi.xml
index b6de7f3..5ab067e 100644
--- a/docbook/reference/en/en-US/modules/social-spi.xml
+++ b/docbook/reference/en/en-US/modules/social-spi.xml
@@ -1,11 +1,12 @@
 <section id="social-spi">
     <title>Social Provider SPI</title>
     <para>
-        Keycloak provides an SPI to make it easy to add additional social providers. This is done by implementing the
-        <ulink url="https://raw.github.com/keycloak/keycloak/master/social/core/src/main/java/org/keycloak/social/SocialProvider.java">SocialProvider</ulink>
-        interface and providing a provider configuration file (<literal>META-INF/services/org.keycloak.social.SocialProvider</literal>).
+        Keycloak provides an SPI to make it easy to add additional social providers. This is done by implementing
+        <literal>org.keycloak.social.SocialProvider</literal> in <literal>social/core</literal>
+        and adding a provider configuration file (<literal>META-INF/services/org.keycloak.social.SocialProvider</literal>).
     </para>
     <para>
-        A good reference for implementing a Social Provider is the <ulink url="https://github.com/keycloak/keycloak/tree/master/social/google">Google provider</ulink>.
+        A good reference for implementing a Social Provider is the Google provider which you can find in <literal>social/google</literal>
+        on GitHub or in the source download.
     </para>
 </section>
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/themes.xml b/docbook/reference/en/en-US/modules/themes.xml
new file mode 100644
index 0000000..4d36919
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/themes.xml
@@ -0,0 +1,163 @@
+<chapter id="themes">
+    <title>Themes</title>
+
+    <para>
+        Keycloak provides theme support for login forms and account management. This allows customizing the look
+        and feel of end-user facing pages so they can be integrated with your brand and applications.
+    </para>
+
+    <section>
+        <title>Configure theme</title>
+        <para>
+            To configure the theme used by a realm open the <literal>Keycloak Admin Console</literal>, select your realm
+            from the drop-down box in the top left corner. In the <literal>Optional Settings</literal> use the drop-down
+            boxes for <literal>Login Theme</literal> and <literal>Account Theme</literal> to select the theme used
+            by login forms and account management pages.
+        </para>
+    </section>
+
+    <section>
+        <title>Creating a theme</title>
+        <para>
+            There are two types of themes in Keycloak, login and account. Login themes are used to customize the
+            login forms, while account themes are used to customize account management. A theme consists of:
+            <itemizedlist>
+                <listitem><para>FreeMarker templates</para></listitem>
+                <listitem><para>Stylesheets</para></listitem>
+                <listitem><para>Scripts</para></listitem>
+                <listitem><para>Images</para></listitem>
+                <listitem><para>Message bundles</para></listitem>
+                <listitem><para>Theme properties</para></listitem>
+            </itemizedlist>
+        </para>
+        <para>
+            A theme can extend another theme. When extending a theme you can override individual files (templates, stylesheets, etc.).
+            The recommended way to create a theme is to extend the base theme. The base theme provides templates
+            and a default message bundle. It should be possible to achieve the customization required by styling these
+            templates.
+        </para>
+        <para>
+            To create a new theme, create a folder in <literal>.../standalone/configuration/themes/login</literal> or
+            <literal>.../standalone/configuration/themes/account</literal>. The name of the folder is the name of the theme.
+            Then create a file <literal>theme.properties</literal> inside the theme folder. The contents of the file should be:
+        </para>
+        <programlisting>parent=base</programlisting>
+        <para>
+            You have now created your theme. Check that it works by configuring it for a realm. It should look the same
+            as the base theme as you've not added anything to it yet. The next sections will describe how to modify
+            the theme.</para>
+        <section>
+            <title>Stylesheets</title>
+            <para>
+                A theme can have one or more stylesheets, to add a stylesheet create a file inside <literal>resources/css</literal> (for example <literal>resources/css/styles.css</literal>)
+                inside your theme folder. Then registering it in <literal>theme.properties</literal> by adding:
+            </para>
+            <programlisting>styles=css/styles.css</programlisting>
+            <para>
+                The <literal>styles</literal> property supports a space separated list so you can add as many
+                as you want. For example:
+            </para>
+            <programlisting>styles=css/styles.css css/more-styles.css</programlisting>
+        </section>
+        <section>
+            <para>
+                A theme can have one or more scripts, to add a script create a file inside <literal>resources/js</literal> (for example <literal>resources/js/script.js</literal>)
+                inside your theme folder. Then registering it in <literal>theme.properties</literal> by adding:
+            </para>
+            <programlisting>scripts=js/script.js</programlisting>
+            <para>
+                The <literal>scripts</literal> property supports a space separated list so you can add as many
+                as you want. For example:
+            </para>
+            <programlisting>scripts=js/script.js js/more-script.js</programlisting>
+        </section>
+        <section>
+            <title>Images</title>
+            <para>
+                To make images available to the theme add them to <literal>resources/img</literal>. They can then be used
+                through stylesheets. For example:
+            </para>
+            <programlisting>body {
+    background-image: url('../img/image.jpg');
+}</programlisting>
+            <para>
+                Or in templates, for example:
+            </para>
+            <programlisting>&lt;img src="${url.resourcesPath}/img/image.jpg"&gt;</programlisting>
+        </section>
+        <section>
+            <title>Messages</title>
+            <para>
+                Text in the templates are loaded from message bundles. Currently internationalization isn't supported,
+                but that will be added in a later release. A theme that extends another theme will inherit all messages
+                from the parents message bundle, but can override individual messages. For example to replace
+                <literal>Username</literal> on the login form with <literal>Your Username</literal> create the file
+                <literal>messages/messages.properties</literal> inside your theme folder and add the following content:
+            </para>
+            <programlisting>username=Your Username</programlisting>
+        </section>
+        <section>
+            <title>Templates</title>
+            <para>
+                For advanced use-cases where you need to modify the html structure it is also possible to override
+                one or more of the templates. For example to override the login page create <literal>login.ftl</literal>
+                inside your theme folder. The base templates all use <literal>template.ftl</literal> to create the
+                basic structure of the page.
+            </para>
+            <para>
+                The base templates are a good reference if you need to create your own templates, they can be
+                found inside <literal>forms/common-themes/src/main/resources/theme</literal> on GitHub or in the source
+                download.
+            </para>
+        </section>
+    </section>
+
+    <section>
+        <title>SPI</title>
+        <para>
+            For full control of login forms and account management Keycloak provides a number of SPIs.
+        </para>
+        <section>
+            <title>Theme SPI</title>
+            <para>
+                The Theme SPI allows creating different mechanisms to providing themes for the default FreeMarker based
+                implementations of login forms and account management. To create a theme provider you will need to implement
+                <literal>org.keycloak.freemarker.ThemeProvider</literal> and <literal>org.keycloak.freemarker.Theme</literal> in
+                <literal>forms/common-freemarker</literal>.
+            </para>
+            <para>
+                Keycloak comes with two theme providers, one that loads themes from the classpath (used by default themes)
+                and another that loads themes from a folder (used by custom themes). Looking at these
+                would be a good place to start to create your own theme provider. You can find them inside
+                <literal>forms/common-themes</literal> on GitHub or the source download.
+            </para>
+        </section>
+        <section>
+            <title>Account SPI</title>
+            <para>
+                The Account SPI allows implementing the account management pages using whatever web framework or templating
+                engine you want. To create an Account provider implement <literal>org.keycloak.account.AccountProvider</literal>
+                and <literal>org.keycloak.account.Account</literal> in <literal>forms/account-api</literal>.
+            </para>
+            <para>
+                Keycloaks default account management provider is built on the FreeMarker template engine (<literal>forms/account-freemarker</literal>).
+                To make sure your provider is loaded you will either need to delete <literal>standalone/deployments/auth-server.war/WEB-INF/lib/keycloak-account-freemarker-1.0-alpha-1.jar</literal>
+                or disable it with the system property <literal>org.keycloak.account.freemarker.FreeMarkerAccountProvider</literal>.
+            </para>
+        </section>
+        <section>
+            <title>Login SPI</title>
+            <para>
+                The Login SPI allows implementing the login forms using whatever web framework or templating
+                engine you want. To create a Login forms provider implement <literal>org.keycloak.login.LoginFormsProvider</literal>
+                and <literal>org.keycloak.login.LoginForms</literal> in <literal>forms/login-api</literal>.
+            </para>
+            <para>
+                Keycloaks default login forms provider is built on the FreeMarker template engine (<literal>forms/login-freemarker</literal>).
+                To make sure your provider is loaded you will either need to delete <literal>standalone/deployments/auth-server.war/WEB-INF/lib/keycloak-login-freemarker-1.0-alpha-1.jar</literal>
+                or disable it with the system property <literal>org.keycloak.login.freemarker.FreeMarkerLoginFormsProvider</literal>.
+            </para>
+        </section>
+    </section>
+
+</chapter>
diff --git a/examples/themes/login/sunrise/resources/css/styles.css b/examples/themes/login/sunrise/resources/css/styles.css
new file mode 100644
index 0000000..bf9d004
--- /dev/null
+++ b/examples/themes/login/sunrise/resources/css/styles.css
@@ -0,0 +1,201 @@
+@import url('../../keycloak/lib/zocial/zocial.css');
+
+body {
+    background-color: #040507;
+    background-image: url('../img/bkgrnd.jpg');
+    background-size: cover;
+    background-repeat: no-repeat;
+
+    color: #ccc;
+    font-family: sans-serif;
+}
+
+a {
+    color: #fff;
+    text-decoration: none;
+}
+
+.content {
+    position: absolute;
+    top: 25%;
+    left: 50%;
+    width: 550px;
+    margin-left: -225px;
+}
+
+h2 {
+    position: fixed;
+    top: 50px;
+    left: 0;
+    width: 100%;
+    text-align: center;
+    margin: 0 auto;
+    color: rgba(255, 255, 255, 0.08);
+    text-shadow: none;
+    font-size: 80px;
+}
+
+div.app-form {
+    float: left;
+    width: 350px;
+}
+
+div.app-form label {
+    display: block;
+    font-size: 16px;
+}
+
+div.social-login {
+    border-left: 1px solid rgba(255, 255, 255, 0.2);
+    float: right;
+    width: 150px;
+    padding: 20px 0 200px 40px;
+}
+
+div.info-area {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    margin-top: 40px;
+    background-color: rgba(0, 0, 0, 0.4);
+    padding: 20px;
+    width: 100%;
+}
+
+div.info-area p {
+    margin-right: 30px;
+    display: inline;
+    text-shadow: none;
+}
+
+input[type=text], input[type=password] {
+    color: #ddd;
+    font-size: 18px;
+    margin-bottom: 20px;
+    background-color: rgba(3,70,114,0.15);
+    border: 0px solid rgba(0,0,0,0.2);
+    box-shadow:inset 0 0 2px 2px rgba(0,0,0,0.15);
+    padding: 10px;
+    width: 296px;
+}
+
+input[type=text]:hover, input[type=password]:hover {
+    background-color: rgba(3,70,114,0.4);
+}
+
+input[type=submit] {
+    border: none;
+
+    background: -webkit-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
+    background: -moz-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
+    background: -ms-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
+    background: -o-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
+
+    box-shadow: 0px 0px 6px rgba(0,0,0,0.5);
+
+    color: rgba(0,0,0,0.6);
+
+    font-size: 14px;
+    font-weight: bold;
+
+    padding: 10px;
+    margin-top: 20px;
+    margin-right: 10px;
+    width: 150px;
+}
+
+input[type=submit]:hover {
+    background-color: rgba(255,255,255,0.8);
+}
+
+p.powered {
+    font-size: 12px;
+    position: fixed;
+    top: 10px;
+    right: 10px;
+    margin: 0;
+    padding: 0;
+    text-shadow: none;
+}
+
+p.powered a {
+    color: #ccc;
+    text-decoration: none;
+}
+
+div.feedback {
+    box-shadow: 0px 0px 6px rgba(0,0,0,0.5);
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    text-align: center;
+}
+
+div.success {
+    background-color: rgba(155,155,255,0.1);
+}
+
+div.warning {
+    background-color: rgba(255,175,0,0.1);
+}
+
+div.error {
+    background-color: rgba(255,0,0,0.1);
+}
+
+div.feedback p {
+    margin: 0;
+    padding: 1em;
+}
+
+div.rcue-logo {
+    background-image: url('../img/logo.png');
+    background-repeat: no-repeat;
+    height: 500px;
+    position: absolute;
+    left: 30px;
+    top: 30px;
+    width: 500px;
+    z-index: -1;
+}
+
+div.social-login span {
+    display: none;
+}
+
+div.social-login p {
+    display: none;
+}
+
+div.social-login ul {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+
+div.social-login ul li {
+    margin-bottom: 20px;
+}
+
+div.social-login ul li span {
+    display: inline;
+    width: 100px;
+}
+
+a.zocial {
+    border: none;
+    background: -webkit-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1)) !important;
+    background: -moz-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1)) !important;
+    background: -ms-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1)) !important;
+    background: -o-linear-gradient(top, rgba(255,255,255,0.8), rgba(255,255,255,0.1)) !important;
+    box-shadow: 0px 0px 6px rgba(0,0,0,0.5);
+    color: rgba(0,0,0,0.6);
+    width: 130px;
+    text-shadow: none;
+    -webkit-border-radius: 0;
+    -moz-border-radius: 0;
+    border-radius: 0;
+    padding-top: 0.2em;
+    padding-bottom: 0.2em;
+}
diff --git a/examples/themes/login/sunrise/resources/img/bkgrnd.jpg b/examples/themes/login/sunrise/resources/img/bkgrnd.jpg
new file mode 100644
index 0000000..bc11ffb
Binary files /dev/null and b/examples/themes/login/sunrise/resources/img/bkgrnd.jpg differ
diff --git a/examples/themes/login/sunrise/resources/img/logo.png b/examples/themes/login/sunrise/resources/img/logo.png
new file mode 100644
index 0000000..6293471
Binary files /dev/null and b/examples/themes/login/sunrise/resources/img/logo.png differ
diff --git a/examples/themes/login/sunrise/theme.properties b/examples/themes/login/sunrise/theme.properties
new file mode 100644
index 0000000..458d39c
--- /dev/null
+++ b/examples/themes/login/sunrise/theme.properties
@@ -0,0 +1,2 @@
+parent=base
+styles=css/styles.css
\ No newline at end of file
diff --git a/forms/account-api/pom.xml b/forms/account-api/pom.xml
new file mode 100755
index 0000000..e9eeb1a
--- /dev/null
+++ b/forms/account-api/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-forms</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-2-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-account-api</artifactId>
+	<name>Keycloak Account Management API</name>
+	<description />
+
+	<dependencies>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/forms/account-api/src/main/java/org/keycloak/account/Account.java b/forms/account-api/src/main/java/org/keycloak/account/Account.java
new file mode 100644
index 0000000..61e92e2
--- /dev/null
+++ b/forms/account-api/src/main/java/org/keycloak/account/Account.java
@@ -0,0 +1,30 @@
+package org.keycloak.account;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Account {
+
+    public Response createResponse(AccountPages page);
+
+    public Account setError(String message);
+
+    public Account setSuccess(String message);
+
+    public Account setWarning(String message);
+
+    public Account setUser(UserModel user);
+
+    public Account setStatus(Response.Status status);
+
+    public Account setRealm(RealmModel realm);
+
+    public Account setReferrer(String referrer);
+
+}
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountLoader.java b/forms/account-api/src/main/java/org/keycloak/account/AccountLoader.java
new file mode 100644
index 0000000..8f9fbec
--- /dev/null
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountLoader.java
@@ -0,0 +1,17 @@
+package org.keycloak.account;
+
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountLoader {
+
+    private AccountLoader() {
+    }
+
+    public static AccountProvider load() {
+        return ServiceLoader.load(AccountProvider.class).iterator().next();
+    }
+
+}
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java b/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
new file mode 100644
index 0000000..2fc9a29
--- /dev/null
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
@@ -0,0 +1,10 @@
+package org.keycloak.account;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public enum AccountPages {
+
+    ACCOUNT, PASSWORD, TOTP;
+
+}
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
new file mode 100644
index 0000000..37ffe89
--- /dev/null
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
@@ -0,0 +1,12 @@
+package org.keycloak.account;
+
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AccountProvider {
+
+    public Account createAccount(UriInfo uriInfo);
+
+}
diff --git a/forms/account-freemarker/pom.xml b/forms/account-freemarker/pom.xml
new file mode 100755
index 0000000..d5db947
--- /dev/null
+++ b/forms/account-freemarker/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-forms</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-2-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-account-freemarker</artifactId>
+	<name>Keycloak Account Management FreeMarker</name>
+	<description />
+
+	<dependencies>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-account-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms-common-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-services</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-social-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
new file mode 100644
index 0000000..14c4a61
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
@@ -0,0 +1,158 @@
+package org.keycloak.account.freemarker;
+
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.account.Account;
+import org.keycloak.account.AccountPages;
+import org.keycloak.account.freemarker.model.AccountBean;
+import org.keycloak.account.freemarker.model.MessageBean;
+import org.keycloak.account.freemarker.model.TotpBean;
+import org.keycloak.account.freemarker.model.UrlBean;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeLoader;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FreeMarkerAccount implements Account {
+
+    private static final Logger logger = Logger.getLogger(FreeMarkerAccount.class);
+
+    private UserModel user;
+    private Response.Status status = Response.Status.OK;
+    private RealmModel realm;
+    private String referrer;
+
+    public static enum MessageType {SUCCESS, WARNING, ERROR}
+
+    private UriInfo uriInfo;
+
+    private String message;
+    private MessageType messageType;
+
+    public FreeMarkerAccount(UriInfo uriInfo) {
+        this.uriInfo = uriInfo;
+    }
+
+    @Override
+    public Response createResponse(AccountPages page) {
+        Map<String, Object> attributes = new HashMap<String, Object>();
+
+        Theme theme;
+        try {
+            theme = ThemeLoader.createTheme(realm.getAccountTheme(), Theme.Type.ACCOUNT);
+        } catch (FreeMarkerException e) {
+            logger.error("Failed to create theme", e);
+            return Response.serverError().build();
+        }
+
+        try {
+            attributes.put("properties", theme.getProperties());
+        } catch (IOException e) {
+            logger.warn("Failed to load properties", e);
+        }
+
+        Properties messages;
+        try {
+            messages = theme.getMessages();
+            attributes.put("rb", messages);
+        } catch (IOException e) {
+            logger.warn("Failed to load messages", e);
+            messages = new Properties();
+        }
+
+        URI baseUri = uriInfo.getBaseUri();
+
+        if (message != null) {
+            attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
+        }
+
+        attributes.put("url", new UrlBean(realm, theme, baseUri, getReferrerUri()));
+
+        switch (page) {
+            case ACCOUNT:
+                attributes.put("account", new AccountBean(user));
+                break;
+            case TOTP:
+                attributes.put("totp", new TotpBean(user, baseUri));
+                break;
+        }
+
+        try {
+            String result = FreeMarkerUtil.processTemplate(attributes, Templates.getTemplate(page), theme);
+            return Response.status(status).type(MediaType.TEXT_HTML).entity(result).build();
+        } catch (FreeMarkerException e) {
+            logger.error("Failed to process template", e);
+            return Response.serverError().build();
+        }
+    }
+
+    private String getReferrerUri() {
+        if (referrer != null) {
+            for (ApplicationModel a : realm.getApplications()) {
+                if (a.getName().equals(referrer)) {
+                    return a.getBaseUrl();
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Account setError(String message) {
+        this.message = message;
+        this.messageType = MessageType.ERROR;
+        return this;
+    }
+
+    @Override
+    public Account setSuccess(String message) {
+        this.message = message;
+        this.messageType = MessageType.SUCCESS;
+        return this;
+    }
+
+    @Override
+    public Account setWarning(String message) {
+        this.message = message;
+        this.messageType = MessageType.WARNING;
+        return this;
+    }
+
+    @Override
+    public Account setUser(UserModel user) {
+        this.user = user;
+        return this;
+    }
+
+    @Override
+    public Account setRealm(RealmModel realm) {
+        this.realm = realm;
+        return this;
+    }
+
+    @Override
+    public Account setStatus(Response.Status status) {
+        this.status = status;
+        return this;
+    }
+
+    @Override
+    public Account setReferrer(String referrer) {
+        this.referrer = referrer;
+        return this;
+    }
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
new file mode 100644
index 0000000..17d8fb9
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -0,0 +1,18 @@
+package org.keycloak.account.freemarker;
+
+import org.keycloak.account.Account;
+import org.keycloak.account.AccountProvider;
+
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FreeMarkerAccountProvider implements AccountProvider {
+
+    @Override
+    public Account createAccount(UriInfo uriInfo) {
+        return new FreeMarkerAccount(uriInfo);
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java
new file mode 100644
index 0000000..b07d38b
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java
@@ -0,0 +1,32 @@
+package org.keycloak.account.freemarker.model;
+
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountBean {
+
+    private UserModel user;
+
+    public AccountBean(UserModel user) {
+        this.user = user;
+    }
+
+    public String getFirstName() {
+        return user.getFirstName();
+    }
+
+    public String getLastName() {
+        return user.getLastName();
+    }
+
+    public String getUsername() {
+        return user.getLoginName();
+    }
+
+    public String getEmail() {
+        return user.getEmail();
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
new file mode 100644
index 0000000..46dbb4e
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
@@ -0,0 +1,63 @@
+package org.keycloak.account.freemarker.model;
+
+import org.keycloak.freemarker.Theme;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.flows.Urls;
+
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UrlBean {
+
+    private String realm;
+    private Theme theme;
+    private URI baseURI;
+    private String referrerURI;
+
+    public UrlBean(RealmModel realm, Theme theme, URI baseURI, String referrerURI) {
+        this.realm = realm.getName();
+        this.theme = theme;
+        this.baseURI = baseURI;
+        this.referrerURI = referrerURI;
+    }
+
+    public String getAccessUrl() {
+        return Urls.accountAccessPage(baseURI, realm).toString();
+    }
+
+    public String getAccountUrl() {
+        return Urls.accountPage(baseURI, realm).toString();
+    }
+
+    public String getPasswordUrl() {
+        return Urls.accountPasswordPage(baseURI, realm).toString();
+    }
+
+    public String getSocialUrl() {
+        return Urls.accountSocialPage(baseURI, realm).toString();
+    }
+
+    public String getTotpUrl() {
+        return Urls.accountTotpPage(baseURI, realm).toString();
+    }
+
+    public String getTotpRemoveUrl() {
+        return Urls.accountTotpRemove(baseURI, realm).toString();
+    }
+
+    public String getLogoutUrl() {
+        return Urls.accountLogout(baseURI, realm).toString();
+    }
+
+    public String getReferrerURI() {
+        return referrerURI;
+    }
+
+    public String getResourcesPath() {
+        URI uri = Urls.themeRoot(baseURI);
+        return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
new file mode 100644
index 0000000..4e701a3
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
@@ -0,0 +1,23 @@
+package org.keycloak.account.freemarker;
+
+import org.keycloak.account.AccountPages;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Templates {
+
+    public static String getTemplate(AccountPages page) {
+        switch (page) {
+            case ACCOUNT:
+                return "account.ftl";
+            case PASSWORD:
+                return "password.ftl";
+            case TOTP:
+                return "totp.ftl";
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/resources/META-INF/services/org.keycloak.account.AccountProvider b/forms/account-freemarker/src/main/resources/META-INF/services/org.keycloak.account.AccountProvider
new file mode 100644
index 0000000..2f97162
--- /dev/null
+++ b/forms/account-freemarker/src/main/resources/META-INF/services/org.keycloak.account.AccountProvider
@@ -0,0 +1 @@
+org.keycloak.account.freemarker.FreeMarkerAccountProvider
\ No newline at end of file
diff --git a/forms/common-freemarker/pom.xml b/forms/common-freemarker/pom.xml
new file mode 100755
index 0000000..bf850f4
--- /dev/null
+++ b/forms/common-freemarker/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-forms</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-2-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-forms-common-freemarker</artifactId>
+	<name>Keycloak Forms Common FreeMarker</name>
+	<description />
+
+	<dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+        </dependency>
+        <dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>    
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/FreeMarkerException.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/FreeMarkerException.java
new file mode 100644
index 0000000..1af9b20
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/FreeMarkerException.java
@@ -0,0 +1,15 @@
+package org.keycloak.freemarker;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FreeMarkerException extends Exception {
+
+    public FreeMarkerException(String message) {
+        super(message);
+    }
+
+    public FreeMarkerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/FreeMarkerUtil.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/FreeMarkerUtil.java
new file mode 100644
index 0000000..8b1c67c
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/FreeMarkerUtil.java
@@ -0,0 +1,53 @@
+package org.keycloak.freemarker;
+
+import freemarker.cache.URLTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FreeMarkerUtil {
+
+    public static String processTemplate(Object data, String templateName, Theme theme) throws FreeMarkerException {
+        Writer out = new StringWriter();
+        Configuration cfg = new Configuration();
+
+        try {
+            cfg.setTemplateLoader(new ThemeTemplateLoader(theme));
+            Template template = cfg.getTemplate(templateName);
+
+            template.process(data, out);
+        } catch (Exception e) {
+            throw new FreeMarkerException("Failed to process template " + templateName, e);
+        }
+
+        return out.toString();
+    }
+
+    public static class ThemeTemplateLoader extends URLTemplateLoader {
+
+        private Theme theme;
+
+        public ThemeTemplateLoader(Theme theme) {
+            this.theme = theme;
+        }
+
+        @Override
+        protected URL getURL(String name) {
+            try {
+                return theme.getTemplate(name);
+            } catch (IOException e) {
+                return null;
+            }
+        }
+
+    }
+
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java
new file mode 100644
index 0000000..c5c7e36
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java
@@ -0,0 +1,33 @@
+package org.keycloak.freemarker;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Theme {
+
+    public enum Type { LOGIN, ACCOUNT };
+
+    public String getName();
+
+    public String getParentName();
+
+    public Type getType();
+
+    public URL getTemplate(String name) throws IOException;
+
+    public InputStream getTemplateAsStream(String name) throws IOException;
+
+    public URL getResource(String path) throws IOException;
+
+    public InputStream getResourceAsStream(String path) throws IOException;
+
+    public Properties getMessages() throws IOException;
+
+    public Properties getProperties() throws IOException;
+
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java
new file mode 100644
index 0000000..f62b4f9
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java
@@ -0,0 +1,166 @@
+package org.keycloak.freemarker;
+
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.util.ProviderLoader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ThemeLoader {
+
+    private static final Logger logger = Logger.getLogger(ThemeLoader.class);
+    public static final String BASE = "base";
+    public static String DEFAULT = BASE;
+
+    public static Theme createTheme(String name, Theme.Type type) throws FreeMarkerException {
+        if (name == null) {
+            name = DEFAULT;
+        }
+
+        Iterable<ThemeProvider> providers = ProviderLoader.load(ThemeProvider.class);
+
+        Theme theme = findTheme(providers, name, type);
+        if (theme.getParentName() != null) {
+            List<Theme> themes = new LinkedList<Theme>();
+            themes.add(theme);
+
+            for (String parentName = theme.getParentName(); parentName != null; parentName = theme.getParentName()) {
+                theme = findTheme(providers, parentName, type);
+                themes.add(theme);
+            }
+
+            return new ExtendingTheme(themes);
+        } else {
+            return theme;
+        }
+    }
+
+    private static Theme findTheme(Iterable<ThemeProvider> providers, String name, Theme.Type type) throws FreeMarkerException {
+        for (ThemeProvider p : providers) {
+            if (p.hasTheme(name, type)) {
+                try {
+                    return p.createTheme(name, type);
+                } catch (IOException e) {
+                    if (name.equals(BASE)) {
+                        throw new FreeMarkerException("Failed to create " + type.toString().toLowerCase() + " theme", e);
+                    } else {
+                        logger.error("Failed to create " + type.toString().toLowerCase() + " theme", e);
+                        return findTheme(providers, BASE, type);
+                    }
+                }
+            }
+        }
+
+        if (name.equals(BASE)) {
+            throw new FreeMarkerException(type.toString().toLowerCase() + " theme '" + name + "' not found");
+        } else {
+            logger.error(type.toString().toLowerCase() + " theme '" + name + "' not found");
+            return findTheme(providers, BASE, type);
+        }
+    }
+
+    public static class ExtendingTheme implements Theme {
+
+        private List<Theme> themes;
+
+        public ExtendingTheme(List<Theme> themes) {
+            this.themes = themes;
+        }
+
+        @Override
+        public String getName() {
+            return themes.get(0).getName();
+        }
+
+        @Override
+        public String getParentName() {
+            return themes.get(0).getParentName();
+        }
+
+        @Override
+        public Type getType() {
+            return themes.get(0).getType();
+        }
+
+        @Override
+        public URL getTemplate(String name) throws IOException {
+            for (Theme t : themes) {
+                URL template = t.getTemplate(name);
+                if (template != null) {
+                    return template;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public InputStream getTemplateAsStream(String name) throws IOException {
+            for (Theme t : themes) {
+                InputStream template = t.getTemplateAsStream(name);
+                if (template != null) {
+                    return template;
+                }
+            }
+            return null;
+        }
+
+
+        @Override
+        public URL getResource(String path) throws IOException {
+            for (Theme t : themes) {
+                URL resource = t.getResource(path);
+                if (resource != null) {
+                    return resource;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public InputStream getResourceAsStream(String path) throws IOException {
+            for (Theme t : themes) {
+                InputStream resource = t.getResourceAsStream(path);
+                if (resource != null) {
+                    return resource;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public Properties getMessages() throws IOException {
+            Properties messages = new Properties();
+            ListIterator<Theme> itr = themes.listIterator(themes.size());
+            while (itr.hasPrevious()) {
+                Properties m = itr.previous().getMessages();
+                if (m != null) {
+                    messages.putAll(m);
+                }
+            }
+            return messages;
+        }
+
+        @Override
+        public Properties getProperties() throws IOException {
+            Properties properties = new Properties();
+            ListIterator<Theme> itr = themes.listIterator(themes.size());
+            while (itr.hasPrevious()) {
+                Properties p = itr.previous().getProperties();
+                if (p != null) {
+                    properties.putAll(p);
+                }
+            }
+            return properties;
+        }
+
+    }
+
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeProvider.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeProvider.java
new file mode 100644
index 0000000..31de058
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeProvider.java
@@ -0,0 +1,17 @@
+package org.keycloak.freemarker;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ThemeProvider {
+
+    public Theme createTheme(String name, Theme.Type type) throws IOException;
+
+    public Set<String> nameSet(Theme.Type type);
+
+    public boolean hasTheme(String name, Theme.Type type);
+
+}
diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml
new file mode 100755
index 0000000..0e6766f
--- /dev/null
+++ b/forms/common-themes/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-forms</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-2-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-forms-common-themes</artifactId>
+	<name>Keycloak Login Default Theme</name>
+	<description />
+
+	<dependencies>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms-common-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
new file mode 100644
index 0000000..d63dc7d
--- /dev/null
+++ b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
@@ -0,0 +1,99 @@
+package org.keycloak.theme;
+
+import org.keycloak.freemarker.Theme;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClassLoaderTheme implements Theme {
+
+    private final String name;
+
+    private final String parentName;
+
+    private final Type type;
+
+    private final String templateRoot;
+
+    private final String resourceRoot;
+
+    private final String messages;
+
+    private final Properties properties;
+
+    public ClassLoaderTheme(String name, Type type) throws IOException {
+        this.name = name;
+        this.type = type;
+
+        String themeRoot = "theme/" + type.toString().toLowerCase() + "/" + name + "/";
+
+        this.templateRoot = themeRoot;
+        this.resourceRoot = themeRoot + "resources/";
+        this.messages = themeRoot + "messages/messages.properties";
+        this.properties = new Properties();
+
+        URL p = getClass().getClassLoader().getResource(themeRoot + "theme.properties");
+        if (p != null) {
+            properties.load(p.openStream());
+            this.parentName = properties.getProperty("parent");
+        } else {
+            this.parentName = null;
+        }
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getParentName() {
+        return parentName;
+    }
+
+    @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public URL getTemplate(String name) {
+        return getClass().getClassLoader().getResource(templateRoot + name);
+    }
+
+    @Override
+    public InputStream getTemplateAsStream(String name) {
+        return getClass().getClassLoader().getResourceAsStream(templateRoot + name);
+    }
+
+    @Override
+    public URL getResource(String path) {
+        return getClass().getClassLoader().getResource(resourceRoot + path);
+    }
+
+    @Override
+    public InputStream getResourceAsStream(String path) {
+        return getClass().getClassLoader().getResourceAsStream(resourceRoot + path);
+    }
+
+    @Override
+    public Properties getMessages() throws IOException {
+        Properties m = new Properties();
+        URL url = getClass().getClassLoader().getResource(this.messages);
+        if (url != null) {
+            m.load(url.openStream());
+        }
+        return m;
+    }
+
+    @Override
+    public Properties getProperties() {
+        return properties;
+    }
+
+}
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java b/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java
new file mode 100644
index 0000000..7d98d3d
--- /dev/null
+++ b/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java
@@ -0,0 +1,55 @@
+package org.keycloak.theme;
+
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeLoader;
+import org.keycloak.freemarker.ThemeProvider;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultLoginThemeProvider implements ThemeProvider {
+
+    public static final String RCUE = "rcue";
+    public static final String KEYCLOAK = "keycloak";
+
+    static {
+        ThemeLoader.DEFAULT = KEYCLOAK;
+    }
+
+    private static Set<String> defaultThemes = new HashSet<String>();
+
+    static {
+        defaultThemes.add(ThemeLoader.BASE);
+        defaultThemes.add(RCUE);
+        defaultThemes.add(KEYCLOAK);
+    }
+
+    @Override
+    public Theme createTheme(String name, Theme.Type type) throws IOException {
+        if (hasTheme(name, type)) {
+            return new ClassLoaderTheme(name, type);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Set<String> nameSet(Theme.Type type) {
+        if (type == Theme.Type.LOGIN || type == Theme.Type.ACCOUNT) {
+            return defaultThemes;
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    @Override
+    public boolean hasTheme(String name, Theme.Type type) {
+        return nameSet(type).contains(name);
+    }
+
+}
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java
new file mode 100644
index 0000000..3737aa1
--- /dev/null
+++ b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java
@@ -0,0 +1,90 @@
+package org.keycloak.theme;
+
+import org.keycloak.freemarker.Theme;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FolderTheme implements Theme {
+
+    private String parentName;
+    private File themeDir;
+    private Type type;
+    private final Properties properties;
+
+    public FolderTheme(File themeDir, Type type) throws IOException {
+        this.themeDir = themeDir;
+        this.type = type;
+        this.properties = new Properties();
+
+        File propertiesFile = new File(themeDir, "theme.properties");
+        if (propertiesFile .isFile()) {
+            properties.load(new FileInputStream(propertiesFile));
+            parentName = properties.getProperty("parent");
+        }
+    }
+
+    @Override
+    public String getName() {
+        return themeDir.getName();
+    }
+
+    @Override
+    public String getParentName() {
+        return parentName;
+    }
+
+    @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public URL getTemplate(String name) throws IOException {
+        File file = new File(themeDir, name);
+        return file.isFile() ? file.toURI().toURL() : null;
+    }
+
+    @Override
+    public InputStream getTemplateAsStream(String name) throws IOException {
+        URL url = getTemplate(name);
+        return url != null ? url.openStream() : null;
+    }
+
+    @Override
+    public URL getResource(String path) throws IOException {
+        if (File.separatorChar != '/') {
+            path = path.replace('/', File.separatorChar);
+        }
+        File file = new File(themeDir, "/resources/" + path);
+        return file.isFile() ? file.toURI().toURL() : null;
+    }
+
+    @Override
+    public InputStream getResourceAsStream(String path) throws IOException {
+        URL url = getResource(path);
+        return url != null ? url.openStream() : null;
+    }
+
+    @Override
+    public Properties getMessages() throws IOException {
+        Properties m = new Properties();
+        File file = new File(themeDir, "messages" + File.separator + "messages.properties");
+        if (file.isFile()) {
+            m.load(new FileInputStream(file));
+        }
+        return m;
+    }
+
+    @Override
+    public Properties getProperties() {
+        return properties;
+    }
+}
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/FolderThemeProvider.java b/forms/common-themes/src/main/java/org/keycloak/theme/FolderThemeProvider.java
new file mode 100644
index 0000000..0d328b2
--- /dev/null
+++ b/forms/common-themes/src/main/java/org/keycloak/theme/FolderThemeProvider.java
@@ -0,0 +1,72 @@
+package org.keycloak.theme;
+
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FolderThemeProvider implements ThemeProvider {
+
+    private File rootDir;
+
+    public FolderThemeProvider() {
+        String d = System.getProperty("keycloak.theme.dir");
+        if (d != null) {
+            rootDir = new File(d);
+        }
+    }
+
+    @Override
+    public Theme createTheme(String name, Theme.Type type) throws IOException {
+        if (hasTheme(name, type)) {
+            return new FolderTheme(new File(getTypeDir(type), name), type);
+        }
+        return null;
+    }
+
+    @Override
+    public Set<String> nameSet(Theme.Type type) {
+        File typeDir = getTypeDir(type);
+        if (typeDir != null) {
+            File[] themes = typeDir.listFiles(new FileFilter() {
+                @Override
+                public boolean accept(File pathname) {
+                    return pathname.isDirectory();
+                }
+            });
+
+            Set<String> names = new HashSet<String>();
+            for (File t : themes) {
+                names.add(t.getName());
+            }
+            return names;
+        }
+
+        return Collections.emptySet();
+    }
+
+    private File getTypeDir(Theme.Type type) {
+        if (rootDir != null && rootDir.isDirectory()) {
+            File typeDir = new File(rootDir, type.name().toLowerCase());
+            if (typeDir.isDirectory()) {
+                return typeDir;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean hasTheme(String name, Theme.Type type) {
+        File typeDir = getTypeDir(type);
+        return typeDir != null && new File(typeDir, name).isDirectory();
+    }
+
+}
diff --git a/forms/common-themes/src/main/resources/META-INF/services/org.keycloak.freemarker.ThemeProvider b/forms/common-themes/src/main/resources/META-INF/services/org.keycloak.freemarker.ThemeProvider
new file mode 100644
index 0000000..8a9390d
--- /dev/null
+++ b/forms/common-themes/src/main/resources/META-INF/services/org.keycloak.freemarker.ThemeProvider
@@ -0,0 +1,2 @@
+org.keycloak.theme.DefaultLoginThemeProvider
+org.keycloak.theme.FolderThemeProvider
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
new file mode 100644
index 0000000..c53e7bc
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
@@ -0,0 +1,27 @@
+authenticatorCode=One-time-password
+email=Email
+errorHeader=Error!
+firstName=First name
+lastName=Last name
+password=Password
+passwordConfirm=Password confirmation
+passwordNew=New Password
+successHeader=Success!
+username=Username
+
+missingFirstName=Please specify first name
+missingLastName=Please specify last name
+missingEmail=Please specify email
+missingPassword=Please specify password.
+notMatchPassword=Passwords don't match
+
+missingTotp=Please specify authenticator code
+invalidPasswordExisting=Invalid existing password
+invalidPasswordConfirm=Password confirmation doesn't match
+invalidTotp=Invalid authenticator code
+
+successTotp=Google authenticator configured.
+successTotpRemoved=Google authenticator removed.
+
+accountUpdated=Your account has been updated
+accountPasswordUpdated=Your password has been updated
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/account/keycloak/resources/css/styles.css b/forms/common-themes/src/main/resources/theme/account/keycloak/resources/css/styles.css
new file mode 100644
index 0000000..2df50df
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/keycloak/resources/css/styles.css
@@ -0,0 +1,13 @@
+@IMPORT url("../../rcue/css/styles.css");
+
+.header.rcue {
+    border-top: none !important;
+}
+
+.header.rcue .navbar.utility {
+    background: #083556 !important;
+}
+
+.header.rcue .navbar-inner {
+    background: #083556 !important;
+}
diff --git a/forms/common-themes/src/main/resources/theme/account/keycloak/theme.properties b/forms/common-themes/src/main/resources/theme/account/keycloak/theme.properties
new file mode 100644
index 0000000..9898eae
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/keycloak/theme.properties
@@ -0,0 +1,2 @@
+parent=rcue
+styles=../rcue/css/styles.css css/styles.css
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/account/rcue/resources/css/styles.css b/forms/common-themes/src/main/resources/theme/account/rcue/resources/css/styles.css
new file mode 100644
index 0000000..41a9d55
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/rcue/resources/css/styles.css
@@ -0,0 +1,14 @@
+@IMPORT url("reset.css");
+
+@IMPORT url("../lib/bootstrap/css/bootstrap.css");
+@IMPORT url("sprites.css");
+
+@IMPORT url("http://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,600,600italic,700,700italic,800,800italic");
+
+@IMPORT url("base.css");
+@IMPORT url("forms.css");
+@IMPORT url("header.css");
+@IMPORT url("icons.css");
+@IMPORT url("tables.css");
+
+@IMPORT url("admin-console.css");
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/account/rcue/theme.properties b/forms/common-themes/src/main/resources/theme/account/rcue/theme.properties
new file mode 100644
index 0000000..298aa65
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/rcue/theme.properties
@@ -0,0 +1,2 @@
+parent=base
+styles=css/styles.css
diff --git a/forms/common-themes/src/main/resources/theme/login/keycloak/resources/css/styles.css b/forms/common-themes/src/main/resources/theme/login/keycloak/resources/css/styles.css
new file mode 100644
index 0000000..5988a48
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/keycloak/resources/css/styles.css
@@ -0,0 +1,4 @@
+body.rcue-login-register {
+    background: #083556;
+    background-image: none;
+}
diff --git a/forms/common-themes/src/main/resources/theme/login/keycloak/theme.properties b/forms/common-themes/src/main/resources/theme/login/keycloak/theme.properties
new file mode 100644
index 0000000..9898eae
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/keycloak/theme.properties
@@ -0,0 +1,2 @@
+parent=rcue
+styles=../rcue/css/styles.css css/styles.css
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/base.css b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/base.css
new file mode 100644
index 0000000..f258d8f
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/base.css
@@ -0,0 +1,52 @@
+* {
+  -moz-box-sizing: border-box;
+  -o-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  font-family: "Open Sans", sans-serif;
+}
+body {
+  height: 100%;
+  width: 100%;
+  font-family: "Open Sans", sans-serif;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-weight: normal;
+  font-family: "Overpass", sans-serif;
+}
+a {
+  color: #0099d3;
+  text-decoration: none;
+}
+a:hover {
+  text-decoration: underline;
+}
+/* Styles from Gabriel */
+strong {
+  font-weight: bold;
+}
+.hidden {
+  display: none;
+}
+.feedback.show {
+  display: inline-block !important;
+}
+.pull-right {
+  float: right;
+}
+.block {
+  display: block;
+}
+a:focus {
+  outline: 0 none;
+}
+.clear-font-size {
+  font-size: 1em;
+}
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/forms.css b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/forms.css
new file mode 100644
index 0000000..c51693c
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/forms.css
@@ -0,0 +1,515 @@
+fieldset {
+  border: none;
+}
+fieldset.border-top {
+    border-color: #E9E8E8;
+    border-style: solid;
+    border-width: 1px 0 0;
+    padding-top: 2em;
+}
+*::-moz-placeholder,
+::-webkit-input-placeholder {
+  color: #838383;
+  font-style: italic;
+}
+input[type="text"],
+input[type="password"],
+input[type="email"] {
+  font-size: 1.1em;
+  padding: 0 0.545454545454545em;
+  min-width: 18.1818181818182em;
+  height: 2.36363636363636em;
+  border: 1px #b6b6b6 solid;
+  border-radius: 2px;
+  box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
+  color: #333;
+}
+input[type="text"]:hover,
+input[type="password"]:hover,
+input[type="email"]:hover {
+  border-color: #62afdb;
+}
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="email"]:focus {
+  border-color: #62afdb;
+  box-shadow: #62afdb 0 0 5px;
+}
+input[type="text"].error,
+input[type="password"].error,
+input[type="email"].error {
+  border-color: #ba1212;
+  transition: all 0.33s ease-in-out;
+  -moz-transition: all 0.33s ease-in-out;
+  -webkit-transition: all 0.33s ease-in-out;
+}
+input[type="text"].error:focus,
+input[type="password"].error:focus,
+input[type="email"].error:focus {
+  box-shadow: 0 0 5px #ba1212;
+}
+input[type="button"],
+button,
+input[type="submit"],
+a.button {
+  font-size: 1.3em;
+  padding: 0.30769230769231em 1.07692307692308em;
+  border-width: 1px;
+  border-radius: 2px;
+  color: #fff;
+  font-weight: bold;
+  letter-spacing: 0.04em;
+  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.15);
+}
+input[type="button"].btn-primary,
+button.btn-primary,
+input[type="submit"].btn-primary,
+a.button.btn-primary {
+  background-image: linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -o-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -moz-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -webkit-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -ms-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #00a9ec), color-stop(1, 0, #009bd3));
+  border-color: #21799e;
+  border-style: solid;
+}
+input[type="button"].btn-primary:hover,
+input[type="submit"].btn-primary:hover,
+button.btn-primary:hover,
+a.button.btn-primary:hover,
+input[type="button"].btn-primary:focus,
+input[type="submit"].btn-primary:focus,
+button.btn-primary:focus,
+a.button.btn-primary:focus {
+  background-color: #009BD3;
+}
+input[type="button"].btn-primary:active,
+input[type="submit"].btn-primary:active,
+button.btn-primary:active,
+a.button.btn-primary:active {
+  background-color: #0099d4;
+}
+input[type="button"].disabled,
+input[type="submit"].disabled,
+button.disabled,
+a.button.disabled {
+  border-color: #cfcdcd;
+  color: #838383;
+  background-color: transparent;
+  background-image: none;
+  box-shadow: none;
+  font-weight: normal;
+  letter-spacing: 0.06363636363636em;
+}
+
+input[type="button"].btn-secondary,
+button.btn-secondary,
+input[type="submit"].btn-secondary,
+a.button.btn-secondary {
+    background-color: #EEEEEE;
+    border-color: #BBBBBB;
+    color: #4D5258;
+}
+
+input[type="button"].disabled:hover,
+input[type="submit"].disabled:hover,
+button.disabled:hover,
+a.button.disabled:hover {
+  cursor: default;
+}
+input[type="button"].disabled:active,
+input[type="submit"].disabled:active,
+button.disabled:active,
+a.button.disabled:active {
+  box-shadow: none;
+}
+input[type="button"]:hover,
+input[type="submit"]:hover,
+button:hover,
+a.button:hover,
+input[type="button"]:focus,
+input[type="submit"]:focus,
+button:focus,
+a.button:focus {
+  background-image: none;
+  cursor: pointer;
+}
+input[type="button"]:active,
+input[type="submit"]:active,
+button:active,
+a.button:active {
+  background-image: none;
+  cursor: pointer;
+  box-shadow: inset 0 0 5px 2px rgba(0, 0, 0, 0.25);
+}
+input[type="checkbox"] {
+  margin-right: 0.5em;
+}
+/* Code from Hylke */
+button,
+a.button {
+  border-color: #21799e;
+  background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -o-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -moz-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -webkit-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -ms-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #ededed));
+  color: #fff;
+  padding: 4px 14px;
+  border: 1px #bbb solid;
+  border-radius: 2px;
+  color: #4d5258;
+  font-weight: bold;
+  font-size: 1.1em;
+  letter-spacing: 0.4px;
+  cursor: pointer;
+  padding-top: 0;
+  padding-bottom: 0;
+  line-height: 2.18181818181818em;
+}
+input[type='submit'].primary,
+button.primary {
+  border-color: #21799e;
+  background-image: linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -o-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -moz-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -webkit-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -ms-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #00a9ec), color-stop(1, 0, #009bd3));
+  color: #fff;
+}
+button.primary:hover,
+button.primary:focus {
+  background-color: #009BD3;
+}
+button.primary:enabled:active {
+  background-color: #0099d4;
+  box-shadow: inset 0 0 5px 3px #0074ae;
+}
+/* Code from Gabriel */
+.rcue-login-register.register .two-fields input[type="text"] {
+  width: 121px;
+  min-width: 0;
+}
+.rcue-login-register.register .two-fields input + input {
+  margin-left: 10px;
+}
+.search-comp {
+  position: relative;
+  display: inline-block;
+  font-size: 0.90909090909091em;
+}
+.search-comp input[type="text"] {
+  padding-right: 2.45454545454545em;
+}
+.search-comp .icon-search {
+  position: absolute;
+  right: 0.2em;
+  top: 0.4em;
+  opacity: 0.5;
+}
+.search-comp .icon-search:hover {
+  opacity: 1;
+  -webkit-transition: ease-in-out opacity 0.25s;
+  -moz-transition: ease-in-out opacity 0.25s;
+  -o-transition: ease-in-out opacity 0.25s;
+  transition: ease-in-out opacity 0.25s;
+}
+.search-comp .icon-search + .tooltip {
+  width: 20em;
+  font-weight: normal;
+}
+.feedback-aligner {
+  position: absolute;
+  top: 1.5em;
+  text-align: center;
+  width: 100%;
+  height: 0;
+  z-index: 100;
+}
+.feedback-aligner .feedback {
+  position: relative;
+  display: inline-block;
+  text-align: left;
+  border-width: 1px;
+}
+.feedback-aligner .feedback p {
+  border-width: 1px;
+}
+.feedback {
+  position: absolute;
+  opacity: 0;
+  transition: opacity 0.33s ease-in-out;
+  -moz-transition: opacity 0.33s ease-in-out;
+  -webkit-transition: opacity 0.33s ease-in-out;
+}
+.feedback p {
+  padding: 0.90909090909091em 3.63636363636364em;
+  border-style: solid;
+  border-width: 1px 1px 0px 1px;
+  background-repeat: no-repeat;
+  background-position: 1.2em center;
+  font-size: 1.1em;
+  line-height: 1.4em;
+  border-radius: 2px;
+  color: #4d5258;
+  margin-bottom: 0;
+}
+.feedback.show {
+  opacity: 1;
+}
+.feedback.bottom-left {
+  background-position: left bottom;
+  background-repeat: no-repeat;
+  padding-bottom: 1em;
+}
+.feedback.bottom-left p {
+  background-position: 1.27272727272727em center;
+}
+.feedback.error {
+  background-image: url(../img/feedback-error-arrow-down.png);
+}
+.feedback.error p {
+  border-color: #b91415;
+  background-image: url(../img/feedback-error-sign.png);
+  background-color: #f8e7e7;
+}
+.feedback.success {
+  background-image: url(../img/feedback-success-arrow-down.png);
+}
+.feedback.success p {
+  border-color: #4b9e39;
+  background-image: url(../img/feedback-success-sign.png);
+  background-color: #e4f1e1;
+}
+.feedback.warning {
+    background-image: url(../img/feedback-warning-arrow-down.png);
+}
+.feedback.warning p {
+  border-color: #f17528;
+  background-image: url(../img/feedback-warning-sign.png);
+  background-color: #fef1e9;
+}
+button,
+a.button {
+  background-color: #eeeeee;
+}
+a.button {
+  display: inline-block;
+}
+a.button:hover {
+  color: #4D5258;
+  text-decoration: none;
+}
+button[class^="icon-"] {
+  border: none;
+  box-shadow: none;
+  background-color: transparent;
+  padding: 0;
+  line-height: 1em;
+}
+
+legend {
+  font-size: 1em;
+  border-width: 1px 0 0 0;
+  border-style: solid;
+  border-color: #e9e8e8;
+  padding-top: 2em;
+  display: block;
+  margin-bottom: 0;
+  padding-bottom: 0.8em;
+  cursor: pointer;
+}
+legend .icon-collapse {
+  vertical-align: baseline;
+}
+legend .text {
+  font-weight: bold;
+  font-size: 1.25em;
+}
+
+.form-group {
+  display: block;
+  margin-bottom: 1em;
+  position: relative;
+}
+.form-group > label {
+  font-size: 1.1em;
+  font-weight: 300;
+  width: 10em;
+  margin-right: 0.90909090909091em;
+  margin-bottom: 0;
+  float: left;
+  margin-top: 0.45454545454545em;
+}
+.form-group > label.two-lines {
+  margin-top: -2px;
+}
+.form-group > label + span {
+  font-size: 1.1em;
+  display: inline-block;
+  margin-top: 0.454545454545455em;
+}
+.form-group > label + .onoffswitch {
+  float: left;
+}
+.form-group > label.pull-left {
+  margin-top: 4px;
+}
+.form-group .required {
+  position: absolute;
+  left: 10em;
+  font-size: 1.1em;
+  color: #CB2915;
+}
+legend + .form-group {
+  padding-top: 1em;
+}
+legend + table {
+  margin-top: 1em;
+}
+.code {
+  font-family: Courier, monospace;
+}
+.onoffswitch {
+  -moz-user-select: none;
+  height: 26px;
+  position: relative;
+  width: 62px;
+}
+.onoffswitch .onoffswitch-checkbox {
+  display: none;
+}
+.onoffswitch .onoffswitch-label {
+  border: 1px solid #bbb;
+  border-radius: 2px;
+  cursor: pointer;
+  display: block;
+  overflow: hidden;
+  width: 62px;
+}
+.onoffswitch .onoffswitch-inner {
+  display: block;
+  margin-left: -100%;
+  transition: margin 0.3s ease-in 0s;
+  width: 200%;
+}
+.onoffswitch .onoffswitch-inner > span {
+  -moz-box-sizing: border-box;
+  color: white;
+  float: left;
+  font-size: 11px;
+  font-family: "Open Sans", sans-serif;
+  font-weight: bold;
+  height: 24px;
+  line-height: 24px;
+  padding: 0;
+  width: 50%;
+}
+.onoffswitch .onoffswitch-switch {
+  background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -o-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -moz-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -webkit-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -ms-linear-gradient(top, #fafafa 0%, #ededed 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #ededed));
+  border: 1px solid #aaa;
+  border-radius: 2px;
+  bottom: 0;
+  margin: 0;
+  position: absolute;
+  right: 39px;
+  top: 0;
+  transition: all 0.3s ease-in 0s;
+  -webkit-transition: all 0.3s ease-in 0s;
+  width: 23px;
+}
+.onoffswitch .onoffswitch-inner .onoffswitch-active {
+  background-image: linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -o-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -moz-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -webkit-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -ms-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #00a9ec), color-stop(1, 0, #009bd3));
+  color: #FFFFFF;
+  padding-left: 10px;
+}
+.onoffswitch .onoffswitch-inner .onoffswitch-inactive {
+  background: linear-gradient(#fefefe, #e8e8e8) repeat scroll 0 0 transparent;
+  color: #4d5258;
+  padding-right: 10px;
+  text-align: right;
+}
+.onoffswitch .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
+  margin-left: 0;
+}
+.onoffswitch .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
+  right: 0;
+}
+input[type="text"].tiny,
+input[type="password"].tiny,
+input[type="email"].tiny {
+  min-width: 40px;
+  width: 40px;
+}
+.select-rcue:hover {
+  border-color: #62afdb;
+}
+.select-rcue:focus {
+  border-color: #62afdb;
+  box-shadow: #62afdb 0 0 5px;
+}
+.select-rcue.error {
+  border-color: #ba1212;
+  background-color: #f8e7e7;
+  transition: all 0.33s ease-in-out;
+  -moz-transition: all 0.33s ease-in-out;
+  -webkit-transition: all 0.33s ease-in-out;
+}
+.select-rcue.error:focus {
+  box-shadow: 0 0 5px #ba1212;
+}
+.select-rcue select {
+  height: 30px;
+  line-height: 30px;
+  margin-top: -2px;
+  margin-left: -2px;
+  font-size: 1.1em;
+  padding: 5px 0.545454545454545em;
+  background-color: transparent;
+  border: none;
+  width: 150%;
+  font-family: "Open Sans", sans-serif;
+}
+.select-rcue option {
+  line-height: 2em;
+  padding-left: 0.90909090909091em;
+}
+.select-rcue option:hover {
+  background-color: #d5ecf9;
+}
+
+.input-group input + .select-rcue {
+  border-radius: 0 2px 2px 0;
+  border-left: 0;
+  display: inline-block;
+}
+.input-select .input-group input {
+  float: left;
+}
+
+.form-actions {
+  float: right;
+  margin-top: 3em;
+  margin-bottom: 5em;
+}
+.form-actions .primary {
+  float: right;
+  margin-left: 0.90909090909091em;
+}
+.form-actions a {
+  font-size: 1.1em;
+  margin-right: 0.90909090909091em;
+}
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/login-register.css b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/login-register.css
new file mode 100644
index 0000000..89222db
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/login-register.css
@@ -0,0 +1,436 @@
+body {
+  font-size: 62.5%;
+  min-height: 60em;
+  min-width: 120em;
+}
+
+body.rcue-login-register {
+    background-image: url("../img/login-screen-background.jpg");
+}
+
+div.rcue-logo {
+    background-image: url("../img/keycloak-logo.png");
+    background-repeat: no-repeat;
+    height: 37px;
+    width: 150px;
+    position: absolute;
+    top: 5em;
+    right: 6.4em;
+}
+
+.rcue-login-register {
+  background-color: #1D2226;
+  background-position: top left;
+  background-size: auto;
+  background-repeat: no-repeat;
+  color: #fff;
+  /* Login area */
+
+  /* Social login area */
+
+  /* Info area */
+
+}
+
+.rcue-login-register h1 a {
+  position: absolute;
+  top: 5em;
+  right: 6.4em;
+}
+.rcue-login-register .content {
+  position: absolute;
+  bottom: 10%;
+  width: 100%;
+  min-width: 76em;
+}
+.rcue-login-register h2 {
+  padding-left: 4.34782608695652em;
+  font-family: "Overpass", sans-serif;
+  font-size: 2.3em;
+  font-weight: 100;
+  text-transform: uppercase;
+  letter-spacing: 0.005em;
+}
+.rcue-login-register h2 strong {
+  font-weight: bold;
+}
+.rcue-login-register .background-area {
+  border-top: 0.1em rgba(255, 255, 255, 0.05) solid;
+  border-bottom: 0.1em rgba(255, 255, 255, 0.05) solid;
+  background-color: rgba(0, 0, 0, 0.3);
+  padding: 3em 0 3em 10em;
+  margin-top: 2.7em;
+  width: 100%;
+  min-width: 120em;
+}
+.rcue-login-register .form-area.separator,
+.rcue-login-register .form-area.social,
+.rcue-login-register .form-area.social.separator {
+  background-repeat: no-repeat;
+  background-position: 42.7em center;
+}
+.rcue-login-register .form-area.separator {
+  background-image: url(../img/login-register-separator.png);
+  background-position: 43.2em center;
+}
+.rcue-login-register .form-area.social {
+  background-image: url(../img/login-register-social.png);
+}
+.rcue-login-register .form-area.social.separator {
+  background-image: url(../img/login-register-social-separator.png);
+}
+.rcue-login-register .background-area .section {
+  float: left;
+  padding: 0 4.5em 0 4.6em;
+  width: auto;
+  position: relative;
+}
+.rcue-login-register .background-area .section,
+.rcue-login-register .background-area .social .section {
+  padding-top: 1.5em;
+  padding-bottom: 1.5em;
+}
+.rcue-login-register .background-area .section h3 {
+  display: none;
+}
+.rcue-login-register .background-area .section:first-child {
+  padding-right: 4.5em;
+}
+.rcue-login-register .section > p {
+  font-size: 1.3em;
+  margin-bottom: 1.53846153846154em;
+  line-height: 1.3em;
+}
+.rcue-login-register .section.app-form {
+  padding-left: 0;
+  position: relative;
+}
+.rcue-login-register form > div {
+  margin-bottom: 1em;
+}
+.rcue-login-register label,
+.rcue-login-register .social-login > p {
+  display: inline-block;
+  font-size: 1.4em;
+  font-weight: 400;
+}
+.rcue-login-register label {
+  width: 8.21428571428571em;
+}
+.rcue-login-register label.two-lines {
+  float: left;
+  margin-top: -0.14285714285714em;
+  line-height: 1.1em;
+}
+.rcue-login-register input[type="text"],
+.rcue-login-register input[type="password"] {
+  width: 24.7272727272727em;
+  /* 272px */
+
+}
+.rcue-login-register form > div.aside-btn {
+  float: left;
+  font-size: 1.1em;
+  margin-left: 10.4545454545454em;
+  margin-top: 0.90909090909091em;
+  margin-bottom: 0;
+}
+.rcue-login-register form > div.aside-btn label {
+  font-size: 1em;
+  width: auto;
+}
+.rcue-login-register form > div.aside-btn input[type="checkbox"] {
+  margin-bottom: 0.54545454545455em;
+  /* 6px */
+}
+.rcue-login-register form > input[type="button"],
+.rcue-login-register form > input[type="submit"]{
+  float: right;
+  margin-top: 0.76923076923077em;
+  margin-left: 0.90909090909091em;
+  /* 10px */
+
+}
+.rcue-login-register p.subtitle {
+  font-size: 1.1em;
+  color: #999;
+  position: absolute;
+  right: 4.09090909090909em;
+  top: -0.636363636363636em;
+}
+.rcue-login-register .feedback.bottom-left {
+  left: 35.7em;
+  bottom: 17em;
+  min-width: 35em;
+}
+.rcue-login-register input.error[type="text"],
+.rcue-login-register input.error[type="password"],
+.rcue-login-register input.error[type="email"] {
+  background-color: #F8E7E7;
+}
+.rcue-login-register .section.social-login > span {
+  display: none;
+}
+.rcue-login-register .section.social-login > p {
+  float: left;
+  margin-top: 0.28571428571429em;
+  /* 14px */
+
+  width: 6.78571428571429em;
+  /* 95px */
+
+}
+.rcue-login-register .section.social-login > ul {
+  float: left;
+}
+.rcue-login-register .section.social-login li {
+  margin-bottom: 2em;
+}
+.rcue-login-register .section.social-login li:last-child {
+  margin-bottom: 0;
+}
+.rcue-login-register .section.info-area {
+  padding-right: 0;
+}
+.rcue-login-register .section.info-area p,
+.rcue-login-register .section.info-area li {
+  font-size: 1.4em;
+  margin-bottom: 1.64285714285714em;
+}
+.rcue-login-register .section.info-area li {
+  color: #999;
+  margin-bottom: 1em;
+}
+.rcue-login-register .section.info-area li:last-child {
+  margin-bottom: 0;
+}
+@media screen and (min-width: 1280px) {
+  .rcue-login-register {
+    background-size: 100% auto;
+  }
+}
+/* Social buttons */
+.zocial,
+a.zocial {
+  padding: 0;
+  line-height: 2.3em;
+  height: 2.3em;
+  width: 131px;
+  border-radius: 2px;
+  box-shadow: none;
+  background-image: none;
+  text-shadow: none;
+}
+.zocial .text,
+a.zocial .text {
+  font-size: 1.2em;
+  line-height: 1.25em;
+  text-align: center;
+  display: block;
+  font-family: "Open Sans", sans-serif;
+  font-weight: normal;
+  border-left: 1px solid rgba(0, 0, 0, 0.15);
+  margin-left: 3em;
+  /* 36 px */
+
+  margin-top: 0.25em;
+  /* 3px */
+
+}
+.zocial:hover,
+a.zocial:hover,
+.zocial:active,
+a.zocial:active,
+.zocial:focus,
+a.zocial:focus {
+  text-decoration: none;
+  background-image: none;
+}
+.zocial:hover,
+a.zocial:hover {
+  background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
+}
+.zocial:before,
+a.zocial:before {
+  margin: 0;
+  padding: 0;
+  box-shadow: none;
+  border: none;
+  width: 3em;
+  /* 36px */
+
+}
+.zocial.facebook:before {
+  width: 2.66666666666667em;
+  /* 32px */
+}
+/* Register page */
+.rcue-login-register.register label {
+  width: 7.5em;
+  /* 105px */
+
+}
+.rcue-login-register.register input[type="text"],
+.rcue-login-register.register input[type="email"],
+.rcue-login-register.register input[type="password"] {
+  width: 22.9090909090909em;
+  /* 252px */
+
+}
+.rcue-login-register.register form > div.aside-btn {
+  margin-left: 9.54545454545454em;
+  /* 105px */
+
+  width: 12.5454545454546em;
+  /* 138px */
+
+}
+.rcue-login-register.register form > div.aside-btn p {
+  line-height: 1.3em;
+}
+.rcue-login-register p.powered {
+  font-size: 1.1em;
+  margin-top: 1.27272727272727em;
+  text-align: right;
+  margin-right: 5.81818181818182em;
+}
+.rcue-login-register p.powered a {
+  color: #666;
+}
+.rcue-login-register p.powered a:hover {
+  color: #0099D3;
+}
+/* Forgot Password page */
+.rcue-login-register.reset .background-area .section.app-form {
+  width: 43.2em;
+}
+.rcue-login-register.oauth .form-actions {
+    margin-bottom: 0;
+    margin-top: 2em;
+}
+.rcue-login-register .background-area .content-area {
+    width: 50em;
+}
+.rcue-login-register .background-area .content-area ul {
+    border-bottom: 1px solid #34393C;
+    margin-bottom: 2em;
+}
+.rcue-login-register .background-area .content-area ul li {
+    border-top: 1px solid #34393C;
+    padding: 2em;
+    position: relative;
+}
+.rcue-login-register .background-area .content-area ul li span {
+    font-size: 1.3em;
+    line-height: 1.3em;
+}
+
+.rcue-login-register .background-area .content-area ul li span:first-child {
+    padding-right: 11.5384615384615em;
+}
+
+.rcue-login-register .background-area .content-area ul li span.parent {
+    position: absolute;
+    left: 26em;
+    top: 1.53846153846154em;
+    width: 12.3076923076923em;
+}
+
+.rcue-login-register .background-area .content-area ul li span.icon-info {
+    float: right;
+    margin-top: 0.5em;
+}
+.rcue-login-register .background-area .content-area p.terms {
+    color: #999999;
+    font-size: 1.1em;
+    line-height: 1.3em;
+}
+
+.rcue-login-register.reset p.subtitle {
+    margin-bottom: 10px;
+    position: inherit;
+    text-align: right;
+}
+
+.rcue-login-register .background-area p.instruction {
+    font-size: 1.3em;
+    line-height: 1.3em;
+    margin-bottom: 1.53846em;
+}
+
+.rcue-login-register .background-area p.instruction.instruction.second {
+    color: #999999;
+}
+.rcue-login-register .background-area p.instruction + .instruction.second {
+    margin-top: -1.23077em;
+}
+
+.rcue-login-register .background-area a.link-right {
+    float: right;
+    font-size: 1.3em;
+}
+
+.rcue-login-register.totp .form-area {
+  background-image: none;
+}
+.rcue-login-register.reset .form-area p.instruction {
+  font-size: 1.3em;
+  line-height: 1.3em;
+  margin-bottom: 1.81818181818182em;
+}
+.rcue-login-register.totp {
+  min-height: 0;
+}
+.rcue-login-register.totp ol li {
+  margin-bottom: 3em;
+  width: 100%;
+}
+.rcue-login-register.totp ol li p {
+  font-size: 1.3em;
+  margin-bottom: 1.92307692307692em;
+}
+.rcue-login-register.totp ol li p strong {
+  text-indent: -1em;
+  float: left;
+  font-size: 1.84615384615385em;
+  font-weight: normal;
+  margin-top: -0.20833333333333em;
+  color: #999;
+}
+.rcue-login-register.totp ol li img {
+  border: 7px solid #fff;
+  width: 150px;
+}
+.rcue-login-register.totp ol li .code {
+  font-size: 1.3em;
+  margin-left: 1.53846153846154em;
+}
+.rcue-login-register.totp ol li form {
+  width: 357px;
+}
+.rcue-login-register.totp ol li form input[type="text"] {
+    width: 22em;
+}
+.rcue-login-register.totp ol li form input[type="submit"] {
+    float: right;
+}
+
+.rcue-login-register.totp ol li:last-child {
+  margin-bottom: 0;
+}
+.rcue-login-register.totp .content {
+  position: inherit;
+  margin-top: 16em;
+}
+.rcue-login-register.email .background-area .section {
+  width: 41.2em;
+}
+.rcue-login-register.email .background-area .section.email {
+  width: 45.8em;
+}
+.rcue-login-register.email label {
+  width: 6.78571428571429em;
+}
+.rcue-login-register.email .feedback.bottom-left {
+  left: 38.3em;
+}
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/reset.css b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/reset.css
new file mode 100644
index 0000000..7f0b5b6
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/rcue/resources/css/reset.css
@@ -0,0 +1,71 @@
+/* http://meyerweb.com/eric/tools/css/reset/ 
+   v2.0 | 20110126
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section {
+	display: block;
+}
+body {
+	line-height: 1;
+}
+ol, ul {
+	list-style: none;
+}
+blockquote, q {
+	quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+
+/* Clearfix */
+
+.clearfix:after {
+	content: ".";
+	display: block;
+	clear: both;
+	visibility: hidden;
+	line-height: 0;
+	height: 0;
+}
+ 
+.clearfix {
+	display: inline-block;
+}
+ 
+html[xmlns] .clearfix {
+	display: block;
+}
+ 
+* html .clearfix {
+	height: 1%;
+}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/customer-login-screen-bg.jpg b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/customer-login-screen-bg.jpg
new file mode 100644
index 0000000..1bbfc95
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/customer-login-screen-bg.jpg differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/favicon.ico b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/favicon.ico
new file mode 100644
index 0000000..8864f00
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/favicon.ico differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-error-arrow-down.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-error-arrow-down.png
new file mode 100644
index 0000000..6f2d9d2
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-error-arrow-down.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-error-sign.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-error-sign.png
new file mode 100644
index 0000000..0dd5004
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-error-sign.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-success-arrow-down.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-success-arrow-down.png
new file mode 100644
index 0000000..03cc0c4
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-success-arrow-down.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-success-sign.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-success-sign.png
new file mode 100644
index 0000000..640bd71
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-success-sign.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-warning-arrow-down.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-warning-arrow-down.png
new file mode 100644
index 0000000..6f2d9d2
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-warning-arrow-down.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-warning-sign.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-warning-sign.png
new file mode 100644
index 0000000..f9392a3
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/feedback-warning-sign.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/keycloak-logo.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/keycloak-logo.png
new file mode 100644
index 0000000..45c51c2
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/keycloak-logo.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-separator.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-separator.png
new file mode 100644
index 0000000..d626a45
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-separator.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-social.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-social.png
new file mode 100644
index 0000000..e86d738
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-social.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-social-separator.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-social-separator.png
new file mode 100644
index 0000000..3f08929
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-register-social-separator.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-screen-background.jpg b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-screen-background.jpg
new file mode 100644
index 0000000..a50a2fc
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/login-screen-background.jpg differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/register-login-bg.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/register-login-bg.png
new file mode 100644
index 0000000..7ddd4ad
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/register-login-bg.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/sprites-white.png b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/sprites-white.png
new file mode 100755
index 0000000..cf70cf1
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/login/rcue/resources/img/sprites-white.png differ
diff --git a/forms/common-themes/src/main/resources/theme/login/rcue/theme.properties b/forms/common-themes/src/main/resources/theme/login/rcue/theme.properties
new file mode 100644
index 0000000..298aa65
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/rcue/theme.properties
@@ -0,0 +1,2 @@
+parent=base
+styles=css/styles.css
diff --git a/forms/login-api/pom.xml b/forms/login-api/pom.xml
new file mode 100755
index 0000000..ed4d5f2
--- /dev/null
+++ b/forms/login-api/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-forms</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-2-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-login-api</artifactId>
+	<name>Keycloak Login API</name>
+	<description />
+
+	<dependencies>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
new file mode 100644
index 0000000..c693be2
--- /dev/null
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
@@ -0,0 +1,49 @@
+package org.keycloak.login;
+
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface LoginForms {
+
+    public Response createResponse(UserModel.RequiredAction action);
+
+    public Response createLogin();
+
+    public Response createPasswordReset();
+
+    public Response createUsernameReminder();
+
+    public Response createLoginTotp();
+
+    public Response createRegistration();
+
+    public Response createErrorPage();
+
+    public Response createOAuthGrant();
+
+    public LoginForms setAccessCode(String accessCodeId, String accessCode);
+
+    public LoginForms setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
+
+    public LoginForms setError(String message);
+
+    public LoginForms setSuccess(String message);
+
+    public LoginForms setWarning(String message);
+
+    public LoginForms setUser(UserModel user);
+
+    public LoginForms setClient(UserModel client);
+
+    public LoginForms setFormData(MultivaluedMap<String, String> formData);
+
+    public LoginForms setStatus(Response.Status status);
+
+}
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsLoader.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsLoader.java
new file mode 100644
index 0000000..817db88
--- /dev/null
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsLoader.java
@@ -0,0 +1,17 @@
+package org.keycloak.login;
+
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginFormsLoader {
+
+    private LoginFormsLoader() {
+    }
+
+    public static LoginFormsProvider load() {
+        return ServiceLoader.load(LoginFormsProvider.class).iterator().next();
+    }
+
+}
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
new file mode 100644
index 0000000..f0d1300
--- /dev/null
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
@@ -0,0 +1,10 @@
+package org.keycloak.login;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public enum LoginFormsPages {
+
+    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, LOGIN_USERNAME_REMINDER, REGISTER, ERROR, LOGIN_UPDATE_PROFILE;
+
+}
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
new file mode 100644
index 0000000..d8e61b4
--- /dev/null
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -0,0 +1,15 @@
+package org.keycloak.login;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.models.RealmModel;
+
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface LoginFormsProvider {
+
+    public LoginForms createForms(RealmModel realm, HttpRequest request, UriInfo uriInfo);
+
+}
diff --git a/forms/login-freemarker/pom.xml b/forms/login-freemarker/pom.xml
new file mode 100755
index 0000000..3922a3d
--- /dev/null
+++ b/forms/login-freemarker/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-forms</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-2-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-login-freemarker</artifactId>
+	<name>Keycloak Login FreeMarker</name>
+	<description />
+
+	<dependencies>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms-common-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-login-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-services</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-social-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>org.jboss</groupId>
+            <artifactId>jboss-vfs</artifactId>
+            <version>3.2.2.Final</version>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
new file mode 100644
index 0000000..30b15fa
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginForms.java
@@ -0,0 +1,273 @@
+package org.keycloak.login.freemarker;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeLoader;
+import org.keycloak.login.LoginForms;
+import org.keycloak.login.LoginFormsPages;
+import org.keycloak.login.freemarker.model.LoginBean;
+import org.keycloak.login.freemarker.model.MessageBean;
+import org.keycloak.login.freemarker.model.OAuthGrantBean;
+import org.keycloak.login.freemarker.model.ProfileBean;
+import org.keycloak.login.freemarker.model.RealmBean;
+import org.keycloak.login.freemarker.model.RegisterBean;
+import org.keycloak.login.freemarker.model.SocialBean;
+import org.keycloak.login.freemarker.model.TotpBean;
+import org.keycloak.login.freemarker.model.UrlBean;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.email.EmailException;
+import org.keycloak.services.email.EmailSender;
+import org.keycloak.services.messages.Messages;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FreeMarkerLoginForms implements LoginForms {
+
+    private static final Logger logger = Logger.getLogger(FreeMarkerLoginForms.class);
+
+    private String message;
+    private String accessCodeId;
+    private String accessCode;
+    private Response.Status status = Response.Status.OK;
+    private List<RoleModel> realmRolesRequested;
+    private MultivaluedMap<String, RoleModel> resourceRolesRequested;
+
+    public static enum MessageType {SUCCESS, WARNING, ERROR}
+
+    private MessageType messageType = MessageType.ERROR;
+
+    private MultivaluedMap<String, String> formData;
+
+    private RealmModel realm;
+
+    // TODO Remove
+    private HttpRequest request;
+
+    private UserModel user;
+
+    private UserModel client;
+
+    private UriInfo uriInfo;
+
+    FreeMarkerLoginForms(RealmModel realm, org.jboss.resteasy.spi.HttpRequest request, UriInfo uriInfo) {
+        this.realm = realm;
+        this.request = request;
+        this.uriInfo = uriInfo;
+    }
+
+    public Response createResponse(UserModel.RequiredAction action) {
+        String actionMessage;
+        LoginFormsPages page;
+
+        switch (action) {
+            case CONFIGURE_TOTP:
+                actionMessage = Messages.ACTION_WARN_TOTP;
+                page = LoginFormsPages.LOGIN_CONFIG_TOTP;
+                break;
+            case UPDATE_PROFILE:
+                actionMessage = Messages.ACTION_WARN_PROFILE;
+                page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
+                break;
+            case UPDATE_PASSWORD:
+                actionMessage = Messages.ACTION_WARN_PASSWD;
+                page = LoginFormsPages.LOGIN_UPDATE_PASSWORD;
+                break;
+            case VERIFY_EMAIL:
+                try {
+                    new EmailSender(realm.getSmtpConfig()).sendEmailVerification(user, realm, accessCodeId, uriInfo);
+                } catch (EmailException e) {
+                    return setError("emailSendError").createErrorPage();
+                }
+
+                actionMessage = Messages.ACTION_WARN_EMAIL;
+                page = LoginFormsPages.LOGIN_VERIFY_EMAIL;
+                break;
+            default:
+                return Response.serverError().build();
+        }
+
+        if (message == null) {
+            setWarning(actionMessage);
+        }
+
+        return createResponse(page);
+    }
+
+    private Response createResponse(LoginFormsPages page) {
+        MultivaluedMap<String, String> queryParameterMap = uriInfo.getQueryParameters();
+
+        String requestURI = uriInfo.getBaseUri().getPath();
+        UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
+
+        for (String k : queryParameterMap.keySet()) {
+            uriBuilder.replaceQueryParam(k, queryParameterMap.get(k).toArray());
+        }
+
+        if (accessCode != null) {
+            uriBuilder.replaceQueryParam("code", accessCode);
+        }
+
+        Map<String, Object> attributes = new HashMap<String, Object>();
+
+        Theme theme;
+        try {
+            theme = ThemeLoader.createTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
+        } catch (FreeMarkerException e) {
+            logger.error("Failed to create theme", e);
+            return Response.serverError().build();
+        }
+
+        try {
+            attributes.put("properties", theme.getProperties());
+        } catch (IOException e) {
+            logger.warn("Failed to load properties", e);
+        }
+
+        Properties messages;
+        try {
+            messages = theme.getMessages();
+            attributes.put("rb", messages);
+        } catch (IOException e) {
+            logger.warn("Failed to load messages", e);
+            messages = new Properties();
+        }
+
+        if (message != null) {
+            attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
+        }
+
+        URI baseUri = uriBuilder.build();
+
+        if (realm != null) {
+            attributes.put("realm", new RealmBean(realm));
+            attributes.put("social", new SocialBean(realm, baseUri));
+            attributes.put("url", new UrlBean(realm, theme, baseUri));
+        }
+
+        attributes.put("login", new LoginBean(formData));
+
+        switch (page) {
+            case LOGIN_CONFIG_TOTP:
+                attributes.put("totp", new TotpBean(user, baseUri));
+                break;
+            case LOGIN_UPDATE_PROFILE:
+                attributes.put("user", new ProfileBean(user));
+                break;
+            case REGISTER:
+                attributes.put("register", new RegisterBean(formData));
+                break;
+            case OAUTH_GRANT:
+                attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested));
+                break;
+        }
+
+        try {
+            String result = FreeMarkerUtil.processTemplate(attributes, Templates.getTemplate(page), theme);
+            return Response.status(status).type(MediaType.TEXT_HTML).entity(result).build();
+        } catch (FreeMarkerException e) {
+            logger.error("Failed to process template", e);
+            return Response.serverError().build();
+        }
+    }
+
+    public Response createLogin() {
+        return createResponse(LoginFormsPages.LOGIN);
+    }
+
+    public Response createPasswordReset() {
+        return createResponse(LoginFormsPages.LOGIN_RESET_PASSWORD);
+    }
+
+    public Response createUsernameReminder() {
+        return createResponse(LoginFormsPages.LOGIN_USERNAME_REMINDER);
+    }
+
+    public Response createLoginTotp() {
+        return createResponse(LoginFormsPages.LOGIN_TOTP);
+    }
+
+    public Response createRegistration() {
+        return createResponse(LoginFormsPages.REGISTER);
+    }
+
+    public Response createErrorPage() {
+        setStatus(Response.Status.INTERNAL_SERVER_ERROR);
+        return createResponse(LoginFormsPages.ERROR);
+    }
+
+    public Response createOAuthGrant() {
+        return createResponse(LoginFormsPages.OAUTH_GRANT);
+    }
+
+    public FreeMarkerLoginForms setError(String message) {
+        this.message = message;
+        this.messageType = MessageType.ERROR;
+        return this;
+    }
+
+    public FreeMarkerLoginForms setSuccess(String message) {
+        this.message = message;
+        this.messageType = MessageType.SUCCESS;
+        return this;
+    }
+
+    public FreeMarkerLoginForms setWarning(String message) {
+        this.message = message;
+        this.messageType = MessageType.WARNING;
+        return this;
+    }
+
+    public FreeMarkerLoginForms setUser(UserModel user) {
+        this.user = user;
+        return this;
+    }
+
+    public FreeMarkerLoginForms setClient(UserModel client) {
+        this.client = client;
+        return this;
+    }
+
+    public FreeMarkerLoginForms setFormData(MultivaluedMap<String, String> formData) {
+        this.formData = formData;
+        return this;
+    }
+
+    @Override
+    public LoginForms setAccessCode(String accessCodeId, String accessCode) {
+        this.accessCodeId = accessCodeId;
+        this.accessCode = accessCode;
+        return this;
+    }
+
+    @Override
+    public LoginForms setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
+        this.realmRolesRequested = realmRolesRequested;
+        this.resourceRolesRequested = resourceRolesRequested;
+        return this;
+    }
+
+    @Override
+    public LoginForms setStatus(Response.Status status) {
+        this.status = status;
+        return this;
+    }
+
+}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
new file mode 100644
index 0000000..b15cada
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -0,0 +1,20 @@
+package org.keycloak.login.freemarker;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.login.LoginForms;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.RealmModel;
+
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
+
+    @Override
+    public LoginForms createForms(RealmModel realm, HttpRequest request, UriInfo uriInfo) {
+        return new FreeMarkerLoginForms(realm, request, uriInfo);
+    }
+
+}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
new file mode 100755
index 0000000..bfc8d86
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
@@ -0,0 +1,86 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.login.freemarker.model;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.Base32;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.Random;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TotpBean {
+
+    private String totpSecret;
+    private String totpSecretEncoded;
+    private boolean enabled;
+    private String contextUrl;
+
+    public TotpBean(UserModel user, URI baseUri) {
+        this.enabled = user.isTotp();
+        this.contextUrl = baseUri.getPath();
+
+        totpSecret = randomString(20);
+        totpSecretEncoded = Base32.encode(totpSecret.getBytes());
+    }
+
+    private static String randomString(int length) {
+        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
+        Random r = new Random();
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            char c = chars.charAt(r.nextInt(chars.length()));
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public String getTotpSecret() {
+        return totpSecret;
+    }
+
+    public String getTotpSecretEncoded() {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < totpSecretEncoded.length(); i += 4) {
+            sb.append(totpSecretEncoded.substring(i, i + 4 < totpSecretEncoded.length() ? i + 4 : totpSecretEncoded.length()));
+            if (i + 4 < totpSecretEncoded.length()) {
+                sb.append(" ");
+            }
+        }
+        return sb.toString();
+    }
+
+    public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
+        String contents = URLEncoder.encode("otpauth://totp/keycloak?secret=" + totpSecretEncoded, "utf-8");
+        return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
+    }
+
+}
+
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
new file mode 100644
index 0000000..ad96e38
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
@@ -0,0 +1,39 @@
+package org.keycloak.login.freemarker;
+
+import org.keycloak.login.LoginFormsPages;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Templates {
+
+    public static String getTemplate(LoginFormsPages page) {
+        switch (page) {
+            case LOGIN:
+                return "login.ftl";
+            case LOGIN_TOTP:
+                return "login-totp.ftl";
+            case LOGIN_CONFIG_TOTP:
+                return "login-config-totp.ftl";
+            case LOGIN_VERIFY_EMAIL:
+                return "login-verify-email.ftl";
+            case OAUTH_GRANT:
+                return "login-oauth-grant.ftl";
+            case LOGIN_RESET_PASSWORD:
+                return "login-reset-password.ftl";
+            case LOGIN_UPDATE_PASSWORD:
+                return "login-update-password.ftl";
+            case LOGIN_USERNAME_REMINDER:
+                return "login-username-reminder.ftl";
+            case REGISTER:
+                return "register.ftl";
+            case ERROR:
+                return "error.ftl";
+            case LOGIN_UPDATE_PROFILE:
+                return "login-update-profile.ftl";
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+}
diff --git a/forms/login-freemarker/src/main/resources/META-INF/services/org.keycloak.login.LoginFormsProvider b/forms/login-freemarker/src/main/resources/META-INF/services/org.keycloak.login.LoginFormsProvider
new file mode 100644
index 0000000..ae28fdb
--- /dev/null
+++ b/forms/login-freemarker/src/main/resources/META-INF/services/org.keycloak.login.LoginFormsProvider
@@ -0,0 +1 @@
+org.keycloak.login.freemarker.FreeMarkerLoginFormsProvider
\ No newline at end of file

forms/pom.xml 53(+9 -44)

diff --git a/forms/pom.xml b/forms/pom.xml
index f444dee..9471ddf 100755
--- a/forms/pom.xml
+++ b/forms/pom.xml
@@ -7,54 +7,19 @@
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
 
 	<artifactId>keycloak-forms</artifactId>
 	<name>Keycloak Forms</name>
 	<description />
 
-	<dependencies>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-core</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-model-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-services</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.keycloak</groupId>
-			<artifactId>keycloak-social-core</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-        <dependency>
-            <groupId>org.jboss.resteasy</groupId>
-            <artifactId>resteasy-jaxrs</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-			<groupId>org.freemarker</groupId>
-			<artifactId>freemarker</artifactId>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
+    <modules>
+        <module>common-freemarker</module>
+        <module>common-themes</module>
+        <module>account-api</module>
+        <module>account-freemarker</module>
+        <module>login-api</module>
+        <module>login-freemarker</module>
+    </modules>
 
 </project>
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index ac75c7a..8ec3e0a 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -161,4 +161,13 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
     Set<RoleModel> getRealmRoleMappings(UserModel user);
 
     Set<RoleModel> getRealmScopeMappings(UserModel user);
+
+    String getLoginTheme();
+
+    void setLoginTheme(String name);
+
+    String getAccountTheme();
+
+    void setAccountTheme(String name);
+
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 58c1f17..4bfbcff 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -50,6 +50,9 @@ public class RealmEntity {
     @Column(length = 2048)
     protected String privateKeyPem;
 
+    protected String loginTheme;
+    protected String accountTheme;
+
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="USER_REQUIRED_CREDENTIALS")
     Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
@@ -274,5 +277,21 @@ public class RealmEntity {
     public void setPasswordPolicy(String passwordPolicy) {
         this.passwordPolicy = passwordPolicy;
     }
+
+    public String getLoginTheme() {
+        return loginTheme;
+    }
+
+    public void setLoginTheme(String theme) {
+        this.loginTheme = theme;
+    }
+
+    public String getAccountTheme() {
+        return accountTheme;
+    }
+
+    public void setAccountTheme(String theme) {
+        this.accountTheme = theme;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 6d14601..0d67e34 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -21,7 +21,7 @@ import java.util.Collection;
 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
 public abstract class RoleEntity {
     @Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     private String id;
 
     private String name;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index b14fa97..965cbbe 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1096,4 +1096,25 @@ public class RealmAdapter implements RealmModel {
         return r.getId().equals(getId());
     }
 
+    @Override
+    public String getLoginTheme() {
+        return realm.getLoginTheme();
+    }
+
+    @Override
+    public void setLoginTheme(String name) {
+        realm.setLoginTheme(name);
+        em.flush();
+    }
+
+    @Override
+    public String getAccountTheme() {
+        return realm.getAccountTheme();
+    }
+
+    @Override
+    public void setAccountTheme(String name) {
+        realm.setAccountTheme(name);
+        em.flush();
+    }
 }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java
index 03c3466..497add8 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java
@@ -27,6 +27,8 @@ public class RealmData extends AbstractPartition {
     private Map<String, String> smtpConfig;
     private Map<String, String> socialConfig;
     private String passwordPolicy;
+    private String loginTheme;
+    private String accountTheme;
 
     public RealmData() {
         super(null);
@@ -185,4 +187,22 @@ public class RealmData extends AbstractPartition {
     public void setPasswordPolicy(String passwordPolicy) {
         this.passwordPolicy = passwordPolicy;
     }
+
+    @AttributeProperty
+    public String getLoginTheme() {
+        return loginTheme;
+    }
+
+    public void setLoginTheme(String theme) {
+        this.loginTheme = theme;
+    }
+
+    @AttributeProperty
+    public String getAccountTheme() {
+        return accountTheme;
+    }
+
+    public void setAccountTheme(String theme) {
+        this.accountTheme = theme;
+    }
 }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmEntity.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmEntity.java
index fe0a448..4b6abb6 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmEntity.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmEntity.java
@@ -62,6 +62,8 @@ public class RealmEntity implements Serializable {
     @AttributeValue
     @Lob
     private HashMap<String, String> socialConfig;
+    @AttributeValue
+    private String theme;
 
 
     public PartitionTypeEntity getPartitionTypeEntity() {
@@ -191,4 +193,12 @@ public class RealmEntity implements Serializable {
     public void setSocialConfig(HashMap<String, String> socialConfig) {
         this.socialConfig = socialConfig;
     }
+
+    public String getTheme() {
+        return theme;
+    }
+
+    public void setTheme(String theme) {
+        this.theme = theme;
+    }
 }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index 4b0b005..d4c1624 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -1014,4 +1014,26 @@ public class RealmAdapter implements RealmModel {
         realm.setPasswordPolicy(policy.toString());
         updateRealm();
     }
+
+    @Override
+    public String getLoginTheme() {
+        return realm.getLoginTheme();
+    }
+
+    @Override
+    public void setLoginTheme(String name) {
+        realm.setLoginTheme(name);
+        updateRealm();
+    }
+
+    @Override
+    public String getAccountTheme() {
+        return realm.getAccountTheme();
+    }
+
+    @Override
+    public void setAccountTheme(String name) {
+        realm.setAccountTheme(name);
+        updateRealm();
+    }
 }

server/pom.xml 27(+26 -1)

diff --git a/server/pom.xml b/server/pom.xml
index db1bf9e..f07ec2a 100755
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -66,7 +66,32 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-forms</artifactId>
+            <artifactId>keycloak-forms-common-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms-common-themes</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-account-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-account-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-login-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-login-freemarker</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
diff --git a/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java b/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java
index 13aa205..fdcdaa4 100755
--- a/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java
+++ b/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java
@@ -10,6 +10,7 @@ import org.keycloak.util.JsonSerialization;
 
 import javax.servlet.ServletContext;
 import javax.ws.rs.core.Context;
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -31,6 +32,14 @@ public class KeycloakServerApplication extends KeycloakApplication {
             session.getTransaction().commit();
         }
 
+        String themeDir = System.getProperty("keycloak.theme.dir");
+        if (themeDir == null) {
+            String jbossConfigDir = System.getProperty("jboss.server.config.dir");
+            if (jbossConfigDir != null) {
+                themeDir = jbossConfigDir + File.separator + "themes";
+                System.setProperty("keycloak.theme.dir", themeDir);
+            }
+        }
     }
 
     public void importRealm(KeycloakSession session, RealmRepresentation rep) {
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index d15233c..8055b83 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -5,18 +5,16 @@
     <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
         <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
         <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
-        <class>org.keycloak.models.jpa.entities.ApplicationScopeMappingEntity</class>
-        <class>org.keycloak.models.jpa.entities.ApplicationUserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
         <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmEntity</class>
-        <class>org.keycloak.models.jpa.entities.RealmScopeMappingEntity</class>
-        <class>org.keycloak.models.jpa.entities.RealmUserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
-        <class>org.keycloak.models.jpa.entities.RoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
 
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
 

services/pom.xml 16(+16 -0)

diff --git a/services/pom.xml b/services/pom.xml
index 7502bbe..d97d6b4 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -32,6 +32,22 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms-common-freemarker</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-account-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-login-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-model-api</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/services/src/main/java/org/keycloak/services/email/EmailSender.java b/services/src/main/java/org/keycloak/services/email/EmailSender.java
index ea76c3d..694157a 100755
--- a/services/src/main/java/org/keycloak/services/email/EmailSender.java
+++ b/services/src/main/java/org/keycloak/services/email/EmailSender.java
@@ -103,9 +103,9 @@ public class EmailSender {
         }
     }
 
-    public void sendEmailVerification(UserModel user, RealmModel realm, AccessCodeEntry accessCode, UriInfo uriInfo) throws EmailException {
+    public void sendEmailVerification(UserModel user, RealmModel realm, String accessCodeId, UriInfo uriInfo) throws EmailException {
         UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
-        builder.queryParam("key", accessCode.getId());
+        builder.queryParam("key", accessCodeId);
 
         URI uri = builder.build(realm.getName());
 
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index f557310..78ee938 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -55,6 +55,9 @@ public class ApplianceBootstrap {
         realm.setRegistrationAllowed(false);
         manager.generateRealmKeys(realm);
 
+        realm.setLoginTheme("keycloak");
+        realm.setAccountTheme("keycloak");
+
         ApplicationModel adminConsole = realm.addApplication(Constants.ADMIN_CONSOLE_APPLICATION);
         adminConsole.setEnabled(true);
         UserCredentialModel adminConsolePassword = new UserCredentialModel();
diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
index 7b14523..a706360 100755
--- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
@@ -73,6 +73,8 @@ public class ModelToRepresentation {
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
         rep.setSmtpServer(realm.getSmtpConfig());
         rep.setSocialProviders(realm.getSocialConfig());
+        rep.setAccountTheme(realm.getAccountTheme());
+        rep.setLoginTheme(realm.getLoginTheme());
         if (realm.getPasswordPolicy() != null) {
             rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
         }
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 3c9a1cd..9afb3ea 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -121,6 +121,8 @@ public class RealmManager {
         if (rep.getRequiredApplicationCredentials() != null) {
             realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
         }
+        realm.setLoginTheme(rep.getLoginTheme());
+        realm.setAccountTheme(rep.getAccountTheme());
 
         realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
 
@@ -198,6 +200,8 @@ public class RealmManager {
             newRealm.setPrivateKeyPem(rep.getPrivateKey());
             newRealm.setPublicKeyPem(rep.getPublicKey());
         }
+        newRealm.setLoginTheme(rep.getLoginTheme());
+        newRealm.setAccountTheme(rep.getAccountTheme());
 
         Map<String, UserModel> userMap = new HashMap<String, UserModel>();
 
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index ef86c57..e63262a 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -24,6 +24,9 @@ package org.keycloak.services.resources;
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.AbstractOAuthClient;
+import org.keycloak.account.Account;
+import org.keycloak.account.AccountLoader;
+import org.keycloak.account.AccountPages;
 import org.keycloak.jaxrs.JaxrsOAuthClient;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
@@ -37,14 +40,11 @@ import org.keycloak.services.managers.ModelToRepresentation;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.FormFlows;
-import org.keycloak.services.resources.flows.Pages;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.*;
-import javax.ws.rs.ext.Providers;
 import java.net.URI;
 import java.util.List;
 
@@ -68,9 +68,6 @@ public class AccountService {
     @Context
     private UriInfo uriInfo;
 
-    @Context
-    private Providers providers;
-
     private AuthenticationManager authManager = new AuthenticationManager();
 
     private ApplicationModel application;
@@ -83,28 +80,28 @@ public class AccountService {
         this.tokenManager = tokenManager;
     }
 
-    private Response forwardToPage(String path, String template) {
+    private Response forwardToPage(String path, AccountPages page) {
         AuthenticationManager.Auth auth = getAuth(false);
         if (auth != null) {
             if (!hasAccess(auth)) {
                 return noAccess();
             }
 
-            FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(auth.getUser());
+            Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
 
             String referrer = getReferrer();
             if (referrer != null) {
-                forms.setQueryParam("referrer", referrer);
+                account.setReferrer(referrer);
             }
 
-            return forms.forwardToForm(template);
+            return account.createResponse(page);
         } else {
             return login(path);
         }
     }
 
     private Response noAccess() {
-        return Flows.forms(realm, request, uriInfo).setError("No access").forwardToErrorPage();
+        return Flows.forms(realm, request, uriInfo).setError("No access").createErrorPage();
     }
 
     @Path("/")
@@ -118,7 +115,7 @@ public class AccountService {
     public Response accountPage() {
         List<MediaType> types = headers.getAcceptableMediaTypes();
         if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
-            return forwardToPage(null, Pages.ACCOUNT);
+            return forwardToPage(null, AccountPages.ACCOUNT);
         } else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
             AuthenticationManager.Auth auth = getAuth(true);
             if (!hasAccess(auth, Constants.ACCOUNT_PROFILE_ROLE)) {
@@ -130,28 +127,16 @@ public class AccountService {
         }
     }
 
-    @Path("social")
-    @GET
-    public Response socialPage() {
-        return forwardToPage("social", Pages.SOCIAL);
-    }
-
     @Path("totp")
     @GET
     public Response totpPage() {
-        return forwardToPage("totp", Pages.TOTP);
+        return forwardToPage("totp", AccountPages.TOTP);
     }
 
     @Path("password")
     @GET
     public Response passwordPage() {
-        return forwardToPage("password", Pages.PASSWORD);
-    }
-
-    @Path("access")
-    @GET
-    public Response accessPage() {
-        return forwardToPage("access", Pages.ACCESS);
+        return forwardToPage("password", AccountPages.PASSWORD);
     }
 
     @Path("/")
@@ -165,16 +150,18 @@ public class AccountService {
 
         UserModel user = auth.getUser();
 
+        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
+
         String error = Validation.validateUpdateProfileForm(formData);
         if (error != null) {
-            return Flows.forms(realm, request, uriInfo).setUser(user).setError(error).forwardToAccount();
+            return account.setError(error).createResponse(AccountPages.ACCOUNT);
         }
 
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
         user.setEmail(formData.getFirst("email"));
 
-        return Flows.forms(realm, request, uriInfo).setUser(user).setSuccess("accountUpdated").forwardToAccount();
+        return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT);
     }
 
     @Path("totp-remove")
@@ -186,9 +173,10 @@ public class AccountService {
         }
 
         UserModel user = auth.getUser();
-
         user.setTotp(false);
-        return Flows.forms(realm, request, uriInfo).setSuccess("successTotpRemoved").setUser(user).forwardToTotp();
+
+        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
+        return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP);
     }
 
     @Path("totp")
@@ -205,11 +193,12 @@ public class AccountService {
         String totp = formData.getFirst("totp");
         String totpSecret = formData.getFirst("totpSecret");
 
-        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
+
         if (Validation.isEmpty(totp)) {
-            return forms.setError(Messages.MISSING_TOTP).forwardToTotp();
+            return account.setError(Messages.MISSING_TOTP).createResponse(AccountPages.TOTP);
         } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
-            return forms.setError(Messages.INVALID_TOTP).forwardToTotp();
+            return account.setError(Messages.INVALID_TOTP).createResponse(AccountPages.TOTP);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
@@ -219,7 +208,7 @@ public class AccountService {
 
         user.setTotp(true);
 
-        return Flows.forms(realm, request, uriInfo).setSuccess("successTotp").setUser(user).forwardToTotp();
+        return account.setSuccess("successTotp").createResponse(AccountPages.TOTP);
     }
 
     @Path("password")
@@ -233,27 +222,27 @@ public class AccountService {
 
         UserModel user = auth.getUser();
 
-        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
 
         String password = formData.getFirst("password");
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
 
         if (Validation.isEmpty(passwordNew)) {
-            return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+            return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
         } else if (!passwordNew.equals(passwordConfirm)) {
-            return forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
+            return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
         }
 
         if (Validation.isEmpty(password)) {
-            return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+            return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
         } else if (!realm.validatePassword(user, password)) {
-            return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
+            return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
         }
 
         String error = Validation.validatePassword(formData, realm.getPasswordPolicy());
         if (error != null) {
-            return forms.setError(error).forwardToPassword();
+            return account.setError(error).createResponse(AccountPages.PASSWORD);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
@@ -262,7 +251,7 @@ public class AccountService {
 
         realm.updateCredential(user, credentials);
 
-        return Flows.forms(realm, request, uriInfo).setUser(user).setSuccess("accountPasswordUpdated").forwardToPassword();
+        return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
     }
 
     @Path("login-redirect")
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java
index a2cdc28..c5ded4d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java
@@ -24,11 +24,8 @@ import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.TokenService;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
-import org.keycloak.util.KeycloakUriBuilder;
 
-import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
-import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.GET;
 import javax.ws.rs.NotAuthorizedException;
 import javax.ws.rs.NotFoundException;
@@ -200,6 +197,30 @@ public class AdminService {
         return adminResource;
     }
 
+    @Path("serverinfo")
+    public ServerInfoAdminResource getServerInfo(@Context final HttpHeaders headers) {
+        RealmManager realmManager = new RealmManager(session);
+        RealmModel saasRealm = getAdminstrationRealm(realmManager);
+        if (saasRealm == null)
+            throw new NotFoundException();
+        UserModel admin = authManager.authenticateSaasIdentity(saasRealm, uriInfo, headers);
+        if (admin == null) {
+            throw new NotAuthorizedException("Bearer");
+        }
+        ApplicationModel adminConsole = saasRealm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
+        if (adminConsole == null) {
+            throw new NotFoundException();
+        }
+        RoleModel adminRole = adminConsole.getRole(Constants.ADMIN_CONSOLE_ADMIN_ROLE);
+        if (!saasRealm.hasRole(admin, adminRole)) {
+            logger.warn("not a Realm admin");
+            throw new NotAuthorizedException("Bearer");
+        }
+        ServerInfoAdminResource adminResource = new ServerInfoAdminResource();
+        resourceContext.initResource(adminResource);
+        return adminResource;
+    }
+
     @Path("login")
     @GET
     @NoCache
@@ -226,7 +247,7 @@ public class AdminService {
     public Response errorOnLoginRedirect(@QueryParam ("error") String message) {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = getAdminstrationRealm(realmManager);
-        return Flows.forms(realm, request, uriInfo).setError(message).forwardToErrorPage();
+        return Flows.forms(realm, request, uriInfo).setError(message).createErrorPage();
     }
 
     protected Response redirectOnLoginError(String message) {
@@ -373,13 +394,11 @@ public class AdminService {
                 NewCookie cookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
                 return Response.status(302).cookie(cookie).location(contextRoot(uriInfo).path(adminPath).build()).build();
             case ACCOUNT_DISABLED:
-                return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData)
-                        .forwardToLogin();
+                return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
             case ACTIONS_REQUIRED:
                 return oauth.processAccessCode(null, "n", contextRoot(uriInfo).path(adminPath).build().toString(), adminConsoleUser, user);
             default:
-                return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
-                        .forwardToLogin();
+                return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java
new file mode 100644
index 0000000..04cda21
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java
@@ -0,0 +1,68 @@
+package org.keycloak.services.resources.admin;
+
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.social.SocialProvider;
+import org.keycloak.util.ProviderLoader;
+
+import javax.ws.rs.GET;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ServerInfoAdminResource {
+
+    @GET
+    public ServerInfoRepresentation getInfo() {
+        ServerInfoRepresentation info = new ServerInfoRepresentation();
+        setSocialProviders(info);
+        setThemes(info);
+        return info;
+    }
+
+    private void setThemes(ServerInfoRepresentation info) {
+        Iterable<ThemeProvider> providers = ProviderLoader.load(ThemeProvider.class);
+        info.themes = new HashMap<String, List<String>>();
+        for (Theme.Type type : Theme.Type.values()) {
+            List<String> themes = new LinkedList<String>();
+            for (ThemeProvider p : providers) {
+                themes.addAll(p.nameSet(type));
+            }
+            Collections.sort(themes);
+            info.themes.put(type.toString().toLowerCase(), themes);
+        }
+    }
+
+    private void setSocialProviders(ServerInfoRepresentation info) {
+        info.socialProviders = new LinkedList<String>();
+        for (SocialProvider p : ProviderLoader.load(SocialProvider.class)) {
+            info.socialProviders.add(p.getId());
+        }
+        Collections.sort(info.socialProviders);
+    }
+
+    public static class ServerInfoRepresentation {
+
+        private Map<String, List<String>> themes;
+
+        private List<String> socialProviders;
+
+        public ServerInfoRepresentation() {
+        }
+
+        public Map<String, List<String>> getThemes() {
+            return themes;
+        }
+
+        public List<String> getSocialProviders() {
+            return socialProviders;
+        }
+
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
index e46632a..5df9493 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
@@ -22,6 +22,8 @@
 package org.keycloak.services.resources.flows;
 
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.login.LoginForms;
+import org.keycloak.login.LoginFormsLoader;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.TokenManager;
@@ -36,8 +38,8 @@ public class Flows {
     private Flows() {
     }
 
-    public static FormFlows forms(RealmModel realm, HttpRequest request, UriInfo uriInfo) {
-        return new FormFlows(realm, request, uriInfo);
+    public static LoginForms forms(RealmModel realm, HttpRequest request, UriInfo uriInfo) {
+        return LoginFormsLoader.load().createForms(realm, request, uriInfo);
     }
 
     public static OAuthFlows oauth(RealmModel realm, HttpRequest request, UriInfo uriInfo, AuthenticationManager authManager,
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index 0ed81dc..a18540f 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -103,14 +103,16 @@ public class OAuthFlows {
         if (!requiredActions.isEmpty()) {
             accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
             accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
-            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
-                    .forwardToAction(user.getRequiredActions().iterator().next());
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
+                    .createResponse(user.getRequiredActions().iterator().next());
         }
 
         if (!isResource
                 && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
             accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
-            return oauthGrantPage(accessCode, client);
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).
+                    setAccessRequest(accessCode.getRealmRolesRequested(), accessCode.getResourceRolesRequested()).
+                    setClient(client).createOAuthGrant();
         }
 
         if (redirect != null) {
@@ -120,18 +122,8 @@ public class OAuthFlows {
         }
     }
 
-    public Response oauthGrantPage(AccessCodeEntry accessCode, UserModel client) {
-        request.setAttribute("realmRolesRequested", accessCode.getRealmRolesRequested());
-        request.setAttribute("resourceRolesRequested", accessCode.getResourceRolesRequested());
-        request.setAttribute("client", client);
-        request.setAttribute("action", TokenService.processOAuthUrl(uriInfo).build(realm.getName()).toString());
-        request.setAttribute("code", accessCode.getCode());
-
-        return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).forwardToOAuthGrant();
-    }
-
     public Response forwardToSecurityFailure(String message) {
-        return Flows.forms(realm, request, uriInfo).setError(message).forwardToErrorPage();
+        return Flows.forms(realm, request, uriInfo).setError(message).createErrorPage();
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 7859c78..23fa68e 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -22,6 +22,7 @@
 package org.keycloak.services.resources.flows;
 
 import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.ThemeResource;
 import org.keycloak.services.resources.admin.AdminService;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.RequiredActionsService;
@@ -29,6 +30,7 @@ import org.keycloak.services.resources.SocialResource;
 import org.keycloak.services.resources.TokenService;
 
 import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
 import java.net.URI;
 
 /**
@@ -128,6 +130,10 @@ public class Urls {
         return tokenBase(baseUri).path(TokenService.class, "registerPage").build(realmId);
     }
 
+    public static URI realmOauthAction(URI baseUri, String realmId) {
+        return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId);
+    }
+
     public static URI realmCode(URI baseUri, String realmId) {
         return tokenBase(baseUri).path(TokenService.class, "accessCodeToToken").build(realmId);
     }
@@ -165,6 +171,10 @@ public class Urls {
                 .build(realmId);
     }
 
+    public static URI themeRoot(URI baseUri) {
+        return themeBase(baseUri).build();
+    }
+
     private static UriBuilder requiredActionsBase(URI baseUri) {
         return tokenBase(baseUri).path(TokenService.class, "getRequiredActionsService");
     }
@@ -172,4 +182,8 @@ public class Urls {
     private static UriBuilder tokenBase(URI baseUri) {
         return realmBase(baseUri).path(RealmsResource.class, "getTokenService");
     }
+
+    private static UriBuilder themeBase(URI baseUri) {
+        return UriBuilder.fromUri(baseUri).path(ThemeResource.class);
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index e41cfc6..6c89c12 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -48,6 +48,7 @@ public class KeycloakApplication extends Application {
         singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
         classes.add(SkeletonKeyContextResolver.class);
         classes.add(QRCodeResource.class);
+        classes.add(ThemeResource.class);
 
         setupDefaultRealm();
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index 327c02d..b38df73 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -23,6 +23,7 @@ package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.login.LoginForms;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.RealmModel;
@@ -38,7 +39,6 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.FormFlows;
 import org.keycloak.services.validation.Validation;
 
 import javax.ws.rs.Consumes;
@@ -97,7 +97,7 @@ public class RequiredActionsService {
 
         String error = Validation.validateUpdateProfileForm(formData);
         if (error != null) {
-            return Flows.forms(realm, request, uriInfo).setError(error).forwardToAction(RequiredAction.UPDATE_PROFILE);
+            return Flows.forms(realm, request, uriInfo).setUser(user).setError(error).createResponse(RequiredAction.UPDATE_PROFILE);
         }
 
         user.setFirstName(formData.getFirst("firstName"));
@@ -124,11 +124,11 @@ public class RequiredActionsService {
         String totp = formData.getFirst("totp");
         String totpSecret = formData.getFirst("totpSecret");
 
-        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        LoginForms loginForms = Flows.forms(realm, request, uriInfo).setUser(user);
         if (Validation.isEmpty(totp)) {
-            return forms.setError(Messages.MISSING_TOTP).forwardToAction(RequiredAction.CONFIGURE_TOTP);
+            return loginForms.setError(Messages.MISSING_TOTP).createResponse(RequiredAction.CONFIGURE_TOTP);
         } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
-            return forms.setError(Messages.INVALID_TOTP).forwardToAction(RequiredAction.CONFIGURE_TOTP);
+            return loginForms.setError(Messages.INVALID_TOTP).createResponse(RequiredAction.CONFIGURE_TOTP);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
@@ -161,16 +161,16 @@ public class RequiredActionsService {
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
 
-        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        LoginForms loginForms = Flows.forms(realm, request, uriInfo).setUser(user);
         if (Validation.isEmpty(passwordNew)) {
-            return forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+            return loginForms.setError(Messages.MISSING_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
         } else if (!passwordNew.equals(passwordConfirm)) {
-            return forms.setError(Messages.NOTMATCH_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+            return loginForms.setError(Messages.NOTMATCH_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
         }
 
         String error = realm.getPasswordPolicy().validate(passwordNew);
         if (error != null) {
-            return forms.setError(error).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+            return loginForms.setError(error).createResponse(RequiredAction.UPDATE_PASSWORD);
         }
 
         UserCredentialModel credentials = new UserCredentialModel();
@@ -186,11 +186,7 @@ public class RequiredActionsService {
             accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
         }
 
-        if (accessCode != null) {
-            return redirectOauth(user, accessCode);
-        } else {
-            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
-        }
+        return redirectOauth(user, accessCode);
     }
 
 
@@ -217,8 +213,8 @@ public class RequiredActionsService {
                 return unauthorized();
             }
 
-            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(accessCode.getUser())
-                    .forwardToAction(RequiredAction.VERIFY_EMAIL);
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(accessCode.getUser())
+                    .createResponse(RequiredAction.VERIFY_EMAIL);
         }
     }
 
@@ -231,9 +227,9 @@ public class RequiredActionsService {
                     || !accessCode.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD)) {
                 return unauthorized();
             }
-            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).createResponse(RequiredAction.UPDATE_PASSWORD);
         } else {
-            return Flows.forms(realm, request, uriInfo).forwardToPasswordReset();
+            return Flows.forms(realm, request, uriInfo).createPasswordReset();
         }
     }
 
@@ -260,7 +256,7 @@ public class RequiredActionsService {
 
         UserModel user = realm.getUserByEmail(email);
         if (user == null) {
-            return Flows.forms(realm, request, uriInfo).setError("emailError").forwardToPasswordReset();
+            return Flows.forms(realm, request, uriInfo).setError("emailError").createPasswordReset();
         }
 
         Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
@@ -274,17 +270,17 @@ public class RequiredActionsService {
             new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
         } catch (EmailException e) {
             logger.error("Failed to send password reset email", e);
-            return Flows.forms(realm, request, uriInfo).setError("emailSendError").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
         }
 
-        return Flows.forms(realm, request, uriInfo).setSuccess("emailSent").forwardToPasswordReset();
+        return Flows.forms(realm, request, uriInfo).setSuccess("emailSent").createPasswordReset();
     }
 
 
     @Path("username-reminder")
     @GET
     public Response usernameReminder() {
-        return Flows.forms(realm, request, uriInfo).forwardToUsernameReminder();
+        return Flows.forms(realm, request, uriInfo).createUsernameReminder();
     }
 
     @Path("username-reminder")
@@ -306,21 +302,21 @@ public class RequiredActionsService {
 
         UserModel user = realm.getUserByEmail(email);
         if (user == null) {
-            return Flows.forms(realm, request, uriInfo).setError("emailError").forwardToUsernameReminder();
+            return Flows.forms(realm, request, uriInfo).setError("emailError").createUsernameReminder();
         }
 
         try {
             new EmailSender(realm.getSmtpConfig()).sendUsernameReminder(user);
         } catch (EmailException e) {
             logger.error("Failed to send username reminder email", e);
-            return Flows.forms(realm, request, uriInfo).setError("emailSendError").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
         }
 
-        return Flows.forms(realm, request, uriInfo).setSuccess("emailUsernameSent").forwardToLogin();
+        return Flows.forms(realm, request, uriInfo).setSuccess("emailUsernameSent").createLogin();
     }
 
     private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
-        String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
+        String code = uriInfo.getQueryParameters().getFirst("code");
         if (code == null) {
             logger.debug("getAccessCodeEntry code as not in query param");
             return null;
@@ -373,8 +369,8 @@ public class RequiredActionsService {
 
         Set<RequiredAction> requiredActions = user.getRequiredActions();
         if (!requiredActions.isEmpty()) {
-            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
-                    .forwardToAction(requiredActions.iterator().next());
+            return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
+                    .createResponse(requiredActions.iterator().next());
         } else {
             logger.debug("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
             accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
@@ -384,7 +380,7 @@ public class RequiredActionsService {
     }
 
     private Response unauthorized() {
-        return Flows.forms(realm, request, uriInfo).setError("Unauthorized request").forwardToErrorPage();
+        return Flows.forms(realm, request, uriInfo).setError("Unauthorized request").createErrorPage();
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index 770d386..a4efab2 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -22,7 +22,6 @@
 package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.models.KeycloakSession;
@@ -38,7 +37,6 @@ import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.social.AuthCallback;
 import org.keycloak.social.AuthRequest;
 import org.keycloak.social.RequestDetails;
-import org.keycloak.social.SocialConstants;
 import org.keycloak.social.SocialLoader;
 import org.keycloak.social.SocialProvider;
 import org.keycloak.social.SocialProviderConfig;
@@ -46,19 +44,13 @@ import org.keycloak.social.SocialProviderException;
 import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.social.SocialUser;
 
-import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
-import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
@@ -67,7 +59,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.UUID;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -204,7 +195,7 @@ public class SocialResource {
 
         SocialProvider provider = SocialLoader.load(providerId);
         if (provider == null) {
-            return Flows.forms(realm, request, uriInfo).setError("Social provider not found").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("Social provider not found").createErrorPage();
         }
 
         String key = realm.getSocialConfig().get(providerId + ".key");
@@ -216,16 +207,16 @@ public class SocialResource {
         UserModel client = realm.getUser(clientId);
         if (client == null) {
             logger.warn("Unknown login requester: " + clientId);
-            return Flows.forms(realm, request, uriInfo).setError("Unknown login requester.").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("Unknown login requester.").createErrorPage();
         }
 
         if (!client.isEnabled()) {
             logger.warn("Login requester not enabled.");
-            return Flows.forms(realm, request, uriInfo).setError("Login requester not enabled.").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("Login requester not enabled.").createErrorPage();
         }
         redirectUri = TokenService.verifyRedirectUri(redirectUri, client);
         if (redirectUri == null) {
-            return Flows.forms(realm, request, uriInfo).setError("Invalid redirect_uri.").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
         }
 
         try {
@@ -240,7 +231,7 @@ public class SocialResource {
 
             return Response.status(Status.FOUND).location(authRequest.getAuthUri()).build();
         } catch (Throwable t) {
-            return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").forwardToErrorPage();
+            return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").createErrorPage();
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java
new file mode 100755
index 0000000..4d06466
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java
@@ -0,0 +1,44 @@
+package org.keycloak.services.resources;
+
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeLoader;
+
+import javax.activation.FileTypeMap;
+import javax.activation.MimetypesFileTypeMap;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Path("/theme")
+public class ThemeResource {
+
+    private static final Logger logger = Logger.getLogger(ThemeResource.class);
+
+    private static FileTypeMap mimeTypes = MimetypesFileTypeMap.getDefaultFileTypeMap();
+
+    @GET
+    @Path("/{themType}/{themeName}/{path:.*}")
+    public Response getResource(@PathParam("themType") String themType, @PathParam("themeName") String themeName, @PathParam("path") String path) {
+        try {
+            Theme theme = ThemeLoader.createTheme(themeName, Theme.Type.valueOf(themType.toUpperCase()));
+            InputStream resource = theme.getResourceAsStream(path);
+            if (resource != null) {
+                return Response.ok(resource).type(mimeTypes.getContentType(path)).build();
+            } else {
+                return Response.status(Response.Status.NOT_FOUND).build();
+            }
+        } catch (Exception e) {
+            logger.warn("Failed to get theme resource", e);
+            return Response.serverError().build();
+        }
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 15beb20..79992d6 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -7,7 +7,6 @@ import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
-import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakTransaction;
@@ -228,8 +227,7 @@ public class TokenService {
         UserModel user = realm.getUser(username);
 
         if (user == null){
-            return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
-                    .forwardToLogin();
+            return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
         }
 
         isTotpConfigurationRequired(user);
@@ -242,13 +240,11 @@ public class TokenService {
             case ACTIONS_REQUIRED:
                 return oauth.processAccessCode(scopeParam, state, redirect, client, user);
             case ACCOUNT_DISABLED:
-                return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData)
-                        .forwardToLogin();
+                return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
             case MISSING_TOTP:
-                return Flows.forms(realm, request, uriInfo).setFormData(formData).forwardToLoginTotp();
+                return Flows.forms(realm, request, uriInfo).setFormData(formData).createLoginTotp();
             default:
-                return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
-                        .forwardToLogin();
+                return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
         }
     }
 
@@ -319,14 +315,14 @@ public class TokenService {
         }
 
         if (error != null) {
-            return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData).forwardToRegistration();
+            return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData).createRegistration();
         }
 
         String username = formData.getFirst("username");
 
         UserModel user = realm.getUser(username);
         if (user != null) {
-            return Flows.forms(realm, request, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).forwardToRegistration();
+            return Flows.forms(realm, request, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration();
         }
 
         user = realm.addUser(username);
@@ -517,8 +513,8 @@ public class TokenService {
         if (prompt != null && prompt.equals("none")) {
             return oauth.redirectError(client, "access_denied", state, redirect);
         }
-        logger.info("forwardToLogin() now...");
-        return Flows.forms(realm, request, uriInfo).forwardToLogin();
+        logger.info("createLogin() now...");
+        return Flows.forms(realm, request, uriInfo).createLogin();
     }
 
     @Path("registrations")
@@ -560,7 +556,7 @@ public class TokenService {
 
         authManager.expireIdentityCookie(realm, uriInfo);
 
-        return Flows.forms(realm, request, uriInfo).forwardToRegistration();
+        return Flows.forms(realm, request, uriInfo).createRegistration();
     }
 
     @Path("logout")
diff --git a/services/src/main/resources/META-INF/mime.types b/services/src/main/resources/META-INF/mime.types
new file mode 100644
index 0000000..432f4c8
--- /dev/null
+++ b/services/src/main/resources/META-INF/mime.types
@@ -0,0 +1 @@
+text/css css CSS
\ No newline at end of file
diff --git a/services/src/test/resources/META-INF/persistence.xml b/services/src/test/resources/META-INF/persistence.xml
index 2fb76f2..a020d60 100755
--- a/services/src/test/resources/META-INF/persistence.xml
+++ b/services/src/test/resources/META-INF/persistence.xml
@@ -2,6 +2,34 @@
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
     version="1.0">
+    <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+        <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
+        <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
+        <class>org.keycloak.models.jpa.entities.RealmEntity</class>
+        <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
+
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+        <properties>
+            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+            <property name="hibernate.connection.username" value="sa"/>
+            <property name="hibernate.connection.password" value=""/>
+            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+            <property name="hibernate.show_sql" value="false" />
+            <property name="hibernate.format_sql" value="true" />
+        </properties>
+    </persistence-unit>
+
     <!--
     <persistence-unit name="picketlink-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
@@ -35,35 +63,4 @@
         </properties>
     </persistence-unit>
     -->
-
-    <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
-        <provider>org.hibernate.ejb.HibernatePersistence</provider>
-
-        <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
-        <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
-        <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
-        <class>org.keycloak.models.jpa.entities.RealmEntity</class>
-        <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
-        <class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
-        <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
-        <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
-
-        <exclude-unlisted-classes>true</exclude-unlisted-classes>
-
-        <properties>
-            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
-            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
-            <property name="hibernate.connection.username" value="sa"/>
-            <property name="hibernate.connection.password" value=""/>
-            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
-            <property name="hibernate.show_sql" value="false" />
-            <property name="hibernate.format_sql" value="true" />
-        </properties>
-    </persistence-unit>
-
-
-
 </persistence>
diff --git a/social/core/pom.xml b/social/core/pom.xml
index 99ade1e..41a9ec3 100755
--- a/social/core/pom.xml
+++ b/social/core/pom.xml
@@ -14,6 +14,11 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.json</groupId>
             <artifactId>json</artifactId>
         </dependency>
diff --git a/social/core/src/main/java/org/keycloak/social/SocialLoader.java b/social/core/src/main/java/org/keycloak/social/SocialLoader.java
index 5dfb44a..6d24535 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialLoader.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialLoader.java
@@ -1,5 +1,7 @@
 package org.keycloak.social;
 
+import org.keycloak.util.ProviderLoader;
+
 import java.util.ServiceLoader;
 
 /**
@@ -25,7 +27,7 @@ public class SocialLoader {
     }
 
     public static Iterable<SocialProvider> load() {
-        return ServiceLoader.load(SocialProvider.class);
+        return ProviderLoader.load(SocialProvider.class);
     }
 
 }
diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java
index 40eb086..0789e23 100644
--- a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java
+++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java
@@ -59,26 +59,6 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
         WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
 
         addJSONData(service.getJSON(deploymentName), warMetaData);
-        //addJSONData(getJSON(), warMetaData);
-    }
-
-    // TODO: remove this.
-    private String getJSON() {
-        return "{\n" +
-"  \"realm\": \"demo\",\n" +
-"  \"realm-public-key\": \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB\",\n" +
-"  \"auth-server-url\": \"http://localhost:8080/auth\",\n" +
-"  \"ssl-not-required\": true,\n" +
-"  \"resource\": \"customer-portal-subsys\",\n" +
-"  \"credentials\": {\n" +
-"    \"password\": \"password\"\n" +
-"  },\n" +
-"  \"use-resource-role-mappings\": false,\n" +
-"  \"enable-cors\": false,\n" +
-"  \"cors-max-age\": -1,\n" +
-"  \"expose-token\": false,\n" +
-"  \"bearer-only\": false\n" +
-"}";
     }
 
     private void addJSONData(String json, WarMetaData warMetaData) {
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index e3fffc5..e228bbe 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -90,7 +90,32 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-forms</artifactId>
+            <artifactId>keycloak-forms-common-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-forms-common-themes</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-account-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-account-freemarker</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-login-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-login-freemarker</artifactId>
             <version>${project.version}</version>
         </dependency>
 
diff --git a/testsuite/integration/README.md b/testsuite/integration/README.md
index 6eb01f1..6428657 100644
--- a/testsuite/integration/README.md
+++ b/testsuite/integration/README.md
@@ -24,6 +24,22 @@ or run org.keycloak.testutils.KeycloakServer from your favourite IDE!
 When starting the server it can also import a realm from a json file:
 
     mvn exec:java -Pkeycloak-server -Dimport=testrealm.json
+    
+### Live edit of html and styles
+
+The Keycloak test server can load resources directly from the filesystem instead of the classpath. This allows editing html, styles and updating images without restarting the server. To make the server use resources from the filesystem start with:
+
+    mvn exec:java -Pkeycloak-server -Dresources
+    
+You can also specify the theme directory used by the server with:
+
+    mvn exec:java -Pkeycloak-server -Dkeycloak.theme.dir=<PATH TO THEMES DIR>
+    
+For example to use the example themes run the server with:
+
+    mvn exec:java -Pkeycloak-server -Dkeycloak.theme.dir=examples/themes
+    
+**NOTE:** If `keycloak.theme.dir` is specified the default themes (base, rcue and keycloak) are loaded from the classpath
 
 TOTP codes
 ----------
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
index c3edfe9..60433a2 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
@@ -37,6 +37,7 @@ import io.undertow.servlet.api.ServletInfo;
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
 import org.jboss.resteasy.spi.ResteasyDeployment;
+import org.keycloak.theme.DefaultLoginThemeProvider;
 import org.keycloak.services.tmp.TmpAdminRedirectServlet;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.models.Constants;
@@ -136,6 +137,11 @@ public class KeycloakServer {
                 throw new RuntimeException("Invalid resources directory");
             }
 
+            if (!System.getProperties().containsKey("keycloak.theme.dir")) {
+                System.setProperty(DefaultLoginThemeProvider.class.getName() + ".disabled", "");
+                System.setProperty("keycloak.theme.dir", file(dir.getAbsolutePath(), "forms", "common-themes", "src", "main", "resources", "theme").getAbsolutePath());
+            }
+
             config.setResourcesHome(dir.getAbsolutePath());
         }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 8df0a1e..c49e8a9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -87,7 +87,7 @@ public class ResetPasswordTest {
 
         resetPasswordPage.assertCurrent();
 
-        Assert.assertEquals("Success!", resetPasswordPage.getMessage());
+        Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getMessage());
 
         Assert.assertEquals(1, greenMail.getReceivedMessages().length);
 
@@ -146,7 +146,7 @@ public class ResetPasswordTest {
 
         resetPasswordPage.assertCurrent();
 
-        Assert.assertEquals("Success!", resetPasswordPage.getMessage());
+        Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getMessage());
 
         Assert.assertEquals(1, greenMail.getReceivedMessages().length);
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index 0a94649..080112f 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -53,7 +53,7 @@ public class AccountPasswordPage extends AbstractAccountPage {
     }
 
     public boolean isCurrent() {
-        return driver.getPageSource().contains("Change Password");
+        return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/password");
     }
 
     public void open() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
index 171e6ef..292a99f 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
@@ -54,7 +54,7 @@ public class AccountTotpPage extends AbstractAccountPage {
     }
 
     public boolean isCurrent() {
-        return driver.getTitle().contains("Edit Account - Google Authenticator");
+        return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/totp");
     }
 
     public void open() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index d19ca29..0c88078 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -75,7 +75,7 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
     }
 
     public boolean isCurrent() {
-        return driver.getPageSource().contains("Edit Account");
+        return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Edit Account");
     }
 
     public void open() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index 3a1f72e..532a7d8 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -59,7 +59,7 @@ public class LoginPage extends AbstractPage {
     @FindBy(linkText = "Username")
     private WebElement recoverUsernameLink;
 
-    @FindBy(id = "loginError")
+    @FindBy(className = "error")
     private WebElement loginErrorMessage;
 
     public void login(String username, String password) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
index 2a7ed57..516f2d6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
@@ -36,7 +36,7 @@ public class LoginTotpPage extends AbstractPage {
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;
 
-    @FindBy(id = "loginError")
+    @FindBy(className = "error")
     private WebElement loginErrorMessage;
 
     public void login(String totp) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
index 9fe02ef..17f058d 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
@@ -41,7 +41,7 @@ public class LoginUpdateProfilePage extends AbstractPage {
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;
 
-    @FindBy(id = "loginError")
+    @FindBy(className = "error")
     private WebElement loginErrorMessage;
 
     public void update(String firstName, String lastName, String email) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index c67d692..c84f1ac 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -50,7 +50,7 @@ public class RegisterPage extends AbstractPage {
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;
 
-    @FindBy(id = "loginError")
+    @FindBy(className = "error")
     private WebElement loginErrorMessage;
 
     public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
index 2f67fde..a020d60 100755
--- a/testsuite/performance/src/test/resources/META-INF/persistence.xml
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -1,7 +1,35 @@
 <persistence xmlns="http://java.sun.com/xml/ns/persistence"
-             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
-             version="1.0">
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+    version="1.0">
+    <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+        <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
+        <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
+        <class>org.keycloak.models.jpa.entities.RealmEntity</class>
+        <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
+
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+        <properties>
+            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+            <property name="hibernate.connection.username" value="sa"/>
+            <property name="hibernate.connection.password" value=""/>
+            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+            <property name="hibernate.show_sql" value="false" />
+            <property name="hibernate.format_sql" value="true" />
+        </properties>
+    </persistence-unit>
+
     <!--
     <persistence-unit name="picketlink-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
@@ -25,47 +53,14 @@
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
 
         <properties>
-            <property name="hibernate.connection.url" value="jdbc:mysql://localhost/keycloakPerfTest"/>
-            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
-            <property name="hibernate.connection.username" value="portal"/>
-            <property name="hibernate.connection.password" value="portal"/>
-            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
-            <!--<property name="hibernate.hbm2ddl.auto" value="update" />-->
-            <property name="hibernate.hbm2ddl.auto" value="${keycloak.jpa.hbm2ddl.auto}" />
+            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+            <property name="hibernate.connection.username" value="sa"/>
+            <property name="hibernate.connection.password" value=""/>
+            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
             <property name="hibernate.show_sql" value="false" />
             <property name="hibernate.format_sql" value="true" />
         </properties>
     </persistence-unit>
-        -->
-    <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
-        <provider>org.hibernate.ejb.HibernatePersistence</provider>
-
-        <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
-        <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
-        <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
-        <class>org.keycloak.models.jpa.entities.RealmEntity</class>
-        <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
-        <class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
-        <class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
-        <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
-        <class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
-
-        <exclude-unlisted-classes>true</exclude-unlisted-classes>
-
-        <properties>
-            <property name="hibernate.connection.url" value="jdbc:mysql://localhost/keycloakPerfTest"/>
-            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
-            <property name="hibernate.connection.username" value="portal"/>
-            <property name="hibernate.connection.password" value="portal"/>
-            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
-            <!--<property name="hibernate.hbm2ddl.auto" value="update" />-->
-            <property name="hibernate.hbm2ddl.auto" value="${keycloak.jpa.hbm2ddl.auto}" />
-            <property name="hibernate.show_sql" value="false" />
-            <property name="hibernate.format_sql" value="true" />
-        </properties>
-    </persistence-unit>
-
-
-</persistence>
\ No newline at end of file
+    -->
+</persistence>