├── .gitignore ├── .babelrc ├── .flowconfig ├── .eslintrc ├── .editorconfig ├── README.md ├── demo └── index.html ├── package.json ├── src └── index.js └── dist ├── vue-keycloak.min.js ├── vue-keycloak.esm.js ├── vue-keycloak.common.js └── vue-keycloak.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/*.map 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "flow-vue"], 3 | "plugins": ["syntax-dynamic-import"] 4 | } 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.* 3 | .*/dist/.* 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [options] 10 | unsafe.enable_getters_and_setters=true 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "plugins": [ 4 | "flowtype" 5 | ], 6 | "extends": [ 7 | "plugin:vue-libs/recommended", 8 | "plugin:flowtype/recommended" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-keycloak 2 | 3 | Use [Keycloak Javascript Adapter](https://keycloak.gitbooks.io/documentation/securing_apps/topics/oidc/javascript-adapter.html) with [Vue.js](https://vuejs.org/) 4 | 5 | ## This is a Work In Progress 6 | 7 | ## How to build 8 | 9 | ```bash 10 | git clone https://github.com/crisbal/vue-keycloak 11 | cd vue-keycloak 12 | npm install 13 | npm run build 14 | ``` 15 | 16 | You will find the built files in `dist/` 17 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-keycloak 6 | 7 | 8 | 9 | 10 |
11 |

Ready: {{ $keycloak.ready }}

12 |

Auth: {{ $keycloak.authenticated }}

13 | 20 | 24 |
25 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-keycloak", 3 | "version": "0.0.11", 4 | "description": "Use Keycloak Javascript Adapter in Vue.js", 5 | "author": "Cristian Baldi", 6 | "license": "MIT", 7 | "main": "dist/vue-keycloak.common.js", 8 | "module": "dist/vue-keycloak.esm.js", 9 | "unpkg": "dist/vue-keycloak.js", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/crisbal/vue-keycloak.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/crisbal/vue-keycloak/issues" 16 | }, 17 | "homepage": "https://github.com/crisbal/vue-keycloak", 18 | "keywords": [ 19 | "vue", 20 | "keycloak" 21 | ], 22 | "scripts": { 23 | "watch": "rollup -wm -c build/dev.config.js", 24 | "build": "npm run lint && node build/build.js", 25 | "lint": "eslint src", 26 | "release": "bash build/release.sh" 27 | }, 28 | "devDependencies": { 29 | "babel-core": "^6.24.1", 30 | "babel-eslint": "^7.2.3", 31 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 32 | "babel-preset-es2015": "^6.24.1", 33 | "babel-preset-flow-vue": "^1.0.0", 34 | "buble": "^0.15.2", 35 | "eslint": "^3.0.1", 36 | "eslint-plugin-flowtype": "^2.34.0", 37 | "eslint-plugin-vue-libs": "^1.2.0", 38 | "flow-bin": "^0.51.0", 39 | "rollup": "^0.45.2", 40 | "rollup-plugin-buble": "^0.15.0", 41 | "rollup-plugin-commonjs": "^8.0.2", 42 | "rollup-plugin-flow-no-whitespace": "^1.0.0", 43 | "rollup-plugin-node-resolve": "^3.0.0", 44 | "rollup-plugin-replace": "^1.1.1", 45 | "rollup-watch": "^4.0.0", 46 | "uglify-js": "^3.0.17", 47 | "vue": "^2.3.0" 48 | }, 49 | "dependencies": { 50 | "keycloak-js": "^3.4.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import Keycloak from 'keycloak-js' 3 | 4 | let installed: Boolean = false 5 | 6 | export default class VueKeyCloak { 7 | static install: () => void 8 | static version: string 9 | } 10 | 11 | VueKeyCloak.install = (Vue, options: Object = { 12 | keycloakOptions: {}, 13 | keycloakInitOptions: {}, 14 | refreshTime: 10 15 | }) => { 16 | if (installed) return 17 | installed = true 18 | 19 | const keycloak: Object = Keycloak(options.keycloakOptions) 20 | 21 | const watch = new Vue({ 22 | data () { 23 | return { 24 | ready: false, 25 | authenticated: false, 26 | user: null, 27 | token: null, 28 | resourceAccess: null 29 | } 30 | } 31 | }) 32 | 33 | keycloak.init(options.keycloakInitOptions).success((isAuthenticated) => { 34 | updateWatchVariables(isAuthenticated).then(() => { 35 | watch.ready = true 36 | }) 37 | 38 | if (isAuthenticated) { 39 | setInterval(() => { 40 | keycloak.updateToken(options.refreshTime + 2) 41 | .success((refreshed) => { 42 | if (refreshed) updateWatchVariables(true) 43 | }) 44 | }, options.refreshTime * 1000) 45 | } 46 | }) 47 | 48 | function updateWatchVariables (isAuthenticated = false) { 49 | watch.authenticated = isAuthenticated 50 | 51 | if (isAuthenticated) { 52 | watch.token = keycloak.token 53 | watch.resourceAccess = keycloak.resourceAccess 54 | return new Promise((resolve, reject) => { 55 | keycloak.loadUserProfile().success((user) => { 56 | watch.user = user 57 | resolve() 58 | }) 59 | }) 60 | } else { 61 | return Promise.resolve() 62 | } 63 | } 64 | 65 | Object.defineProperty(Vue.prototype, '$keycloak', { 66 | get () { 67 | keycloak.ready = watch.ready 68 | keycloak.user = watch.user 69 | return keycloak 70 | } 71 | }) 72 | } 73 | 74 | VueKeyCloak.version = '__VERSION__' 75 | -------------------------------------------------------------------------------- /dist/vue-keycloak.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vue-keycloak v0.0.11 3 | * (c) Cristian Baldi 2018 4 | */ 5 | !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):e.VueKeycloak=r()}(this,function(){"use strict";var e,s=(function(e){var U,t;U=window,t=function(i){if(!(this instanceof t))return new t(i);for(var a,u,d=this,c=[],l={enable:!0,callbackList:[],interval:5},e=document.getElementsByTagName("script"),r=0;r= 0; --i) { 872 | var promise = callbacks[i]; 873 | if (event.data == 'unchanged') { 874 | promise.setSuccess(); 875 | } else { 876 | promise.setError(); 877 | } 878 | } 879 | }; 880 | 881 | window.addEventListener('message', messageCallback, false); 882 | 883 | var check = function() { 884 | checkLoginIframe(); 885 | if (kc.token) { 886 | setTimeout(check, loginIframe.interval * 1000); 887 | } 888 | }; 889 | 890 | return promise.promise; 891 | } 892 | 893 | function checkLoginIframe() { 894 | var promise = createPromise(); 895 | 896 | if (loginIframe.iframe && loginIframe.iframeOrigin ) { 897 | var msg = kc.clientId + ' ' + kc.sessionId; 898 | loginIframe.callbackList.push(promise); 899 | var origin = loginIframe.iframeOrigin; 900 | if (loginIframe.callbackList.length == 1) { 901 | loginIframe.iframe.contentWindow.postMessage(msg, origin); 902 | } 903 | } else { 904 | promise.setSuccess(); 905 | } 906 | 907 | return promise.promise; 908 | } 909 | 910 | function loadAdapter(type) { 911 | if (!type || type == 'default') { 912 | return { 913 | login: function(options) { 914 | window.location.href = kc.createLoginUrl(options); 915 | return createPromise().promise; 916 | }, 917 | 918 | logout: function(options) { 919 | window.location.href = kc.createLogoutUrl(options); 920 | return createPromise().promise; 921 | }, 922 | 923 | register: function(options) { 924 | window.location.href = kc.createRegisterUrl(options); 925 | return createPromise().promise; 926 | }, 927 | 928 | accountManagement : function() { 929 | window.location.href = kc.createAccountUrl(); 930 | return createPromise().promise; 931 | }, 932 | 933 | redirectUri: function(options, encodeHash) { 934 | if (arguments.length == 1) { 935 | encodeHash = true; 936 | } 937 | 938 | if (options && options.redirectUri) { 939 | return options.redirectUri; 940 | } else if (kc.redirectUri) { 941 | return kc.redirectUri; 942 | } else { 943 | var redirectUri = location.href; 944 | if (location.hash && encodeHash) { 945 | redirectUri = redirectUri.substring(0, location.href.indexOf('#')); 946 | redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); 947 | } 948 | return redirectUri; 949 | } 950 | } 951 | }; 952 | } 953 | 954 | if (type == 'cordova') { 955 | loginIframe.enable = false; 956 | var cordovaOpenWindowWrapper = function(loginUrl, target, options) { 957 | if (window.cordova && window.cordova.InAppBrowser) { 958 | // Use inappbrowser for IOS and Android if available 959 | return window.cordova.InAppBrowser.open(loginUrl, target, options); 960 | } else { 961 | return window.open(loginUrl, target, options); 962 | } 963 | }; 964 | return { 965 | login: function(options) { 966 | var promise = createPromise(); 967 | 968 | var o = 'location=no'; 969 | if (options && options.prompt == 'none') { 970 | o += ',hidden=yes'; 971 | } 972 | 973 | var loginUrl = kc.createLoginUrl(options); 974 | var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o); 975 | var completed = false; 976 | 977 | ref.addEventListener('loadstart', function(event) { 978 | if (event.url.indexOf('http://localhost') == 0) { 979 | var callback = parseCallback(event.url); 980 | processCallback(callback, promise); 981 | ref.close(); 982 | completed = true; 983 | } 984 | }); 985 | 986 | ref.addEventListener('loaderror', function(event) { 987 | if (!completed) { 988 | if (event.url.indexOf('http://localhost') == 0) { 989 | var callback = parseCallback(event.url); 990 | processCallback(callback, promise); 991 | ref.close(); 992 | completed = true; 993 | } else { 994 | promise.setError(); 995 | ref.close(); 996 | } 997 | } 998 | }); 999 | 1000 | return promise.promise; 1001 | }, 1002 | 1003 | logout: function(options) { 1004 | var promise = createPromise(); 1005 | 1006 | var logoutUrl = kc.createLogoutUrl(options); 1007 | var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); 1008 | 1009 | var error; 1010 | 1011 | ref.addEventListener('loadstart', function(event) { 1012 | if (event.url.indexOf('http://localhost') == 0) { 1013 | ref.close(); 1014 | } 1015 | }); 1016 | 1017 | ref.addEventListener('loaderror', function(event) { 1018 | if (event.url.indexOf('http://localhost') == 0) { 1019 | ref.close(); 1020 | } else { 1021 | error = true; 1022 | ref.close(); 1023 | } 1024 | }); 1025 | 1026 | ref.addEventListener('exit', function(event) { 1027 | if (error) { 1028 | promise.setError(); 1029 | } else { 1030 | kc.clearToken(); 1031 | promise.setSuccess(); 1032 | } 1033 | }); 1034 | 1035 | return promise.promise; 1036 | }, 1037 | 1038 | register : function() { 1039 | var registerUrl = kc.createRegisterUrl(); 1040 | var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no'); 1041 | ref.addEventListener('loadstart', function(event) { 1042 | if (event.url.indexOf('http://localhost') == 0) { 1043 | ref.close(); 1044 | } 1045 | }); 1046 | }, 1047 | 1048 | accountManagement : function() { 1049 | var accountUrl = kc.createAccountUrl(); 1050 | var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); 1051 | ref.addEventListener('loadstart', function(event) { 1052 | if (event.url.indexOf('http://localhost') == 0) { 1053 | ref.close(); 1054 | } 1055 | }); 1056 | }, 1057 | 1058 | redirectUri: function(options) { 1059 | return 'http://localhost'; 1060 | } 1061 | } 1062 | } 1063 | 1064 | throw 'invalid adapter type: ' + type; 1065 | } 1066 | 1067 | var LocalStorage = function() { 1068 | if (!(this instanceof LocalStorage)) { 1069 | return new LocalStorage(); 1070 | } 1071 | 1072 | localStorage.setItem('kc-test', 'test'); 1073 | localStorage.removeItem('kc-test'); 1074 | 1075 | var cs = this; 1076 | 1077 | function clearExpired() { 1078 | var time = new Date().getTime(); 1079 | for (var i = 0; i < localStorage.length; i++) { 1080 | var key = localStorage.key(i); 1081 | if (key && key.indexOf('kc-callback-') == 0) { 1082 | var value = localStorage.getItem(key); 1083 | if (value) { 1084 | try { 1085 | var expires = JSON.parse(value).expires; 1086 | if (!expires || expires < time) { 1087 | localStorage.removeItem(key); 1088 | } 1089 | } catch (err) { 1090 | localStorage.removeItem(key); 1091 | } 1092 | } 1093 | } 1094 | } 1095 | } 1096 | 1097 | cs.get = function(state) { 1098 | if (!state) { 1099 | return; 1100 | } 1101 | 1102 | var key = 'kc-callback-' + state; 1103 | var value = localStorage.getItem(key); 1104 | if (value) { 1105 | localStorage.removeItem(key); 1106 | value = JSON.parse(value); 1107 | } 1108 | 1109 | clearExpired(); 1110 | return value; 1111 | }; 1112 | 1113 | cs.add = function(state) { 1114 | clearExpired(); 1115 | 1116 | var key = 'kc-callback-' + state.state; 1117 | state.expires = new Date().getTime() + (60 * 60 * 1000); 1118 | localStorage.setItem(key, JSON.stringify(state)); 1119 | }; 1120 | }; 1121 | 1122 | var CookieStorage = function() { 1123 | if (!(this instanceof CookieStorage)) { 1124 | return new CookieStorage(); 1125 | } 1126 | 1127 | var cs = this; 1128 | 1129 | cs.get = function(state) { 1130 | if (!state) { 1131 | return; 1132 | } 1133 | 1134 | var value = getCookie('kc-callback-' + state); 1135 | setCookie('kc-callback-' + state, '', cookieExpiration(-100)); 1136 | if (value) { 1137 | return JSON.parse(value); 1138 | } 1139 | }; 1140 | 1141 | cs.add = function(state) { 1142 | setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60)); 1143 | }; 1144 | 1145 | cs.removeItem = function(key) { 1146 | setCookie(key, '', cookieExpiration(-100)); 1147 | }; 1148 | 1149 | var cookieExpiration = function (minutes) { 1150 | var exp = new Date(); 1151 | exp.setTime(exp.getTime() + (minutes*60*1000)); 1152 | return exp; 1153 | }; 1154 | 1155 | var getCookie = function (key) { 1156 | var name = key + '='; 1157 | var ca = document.cookie.split(';'); 1158 | for (var i = 0; i < ca.length; i++) { 1159 | var c = ca[i]; 1160 | while (c.charAt(0) == ' ') { 1161 | c = c.substring(1); 1162 | } 1163 | if (c.indexOf(name) == 0) { 1164 | return c.substring(name.length, c.length); 1165 | } 1166 | } 1167 | return ''; 1168 | }; 1169 | 1170 | var setCookie = function (key, value, expirationDate) { 1171 | var cookie = key + '=' + value + '; ' 1172 | + 'expires=' + expirationDate.toUTCString() + '; '; 1173 | document.cookie = cookie; 1174 | }; 1175 | }; 1176 | 1177 | function createCallbackStorage() { 1178 | try { 1179 | return new LocalStorage(); 1180 | } catch (err) { 1181 | } 1182 | 1183 | return new CookieStorage(); 1184 | } 1185 | 1186 | var CallbackParser = function(uriToParse, responseMode) { 1187 | if (!(this instanceof CallbackParser)) { 1188 | return new CallbackParser(uriToParse, responseMode); 1189 | } 1190 | var parser = this; 1191 | 1192 | var initialParse = function() { 1193 | var baseUri = null; 1194 | var queryString = null; 1195 | var fragmentString = null; 1196 | 1197 | var questionMarkIndex = uriToParse.indexOf("?"); 1198 | var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1); 1199 | if (questionMarkIndex == -1 && fragmentIndex == -1) { 1200 | baseUri = uriToParse; 1201 | } else if (questionMarkIndex != -1) { 1202 | baseUri = uriToParse.substring(0, questionMarkIndex); 1203 | queryString = uriToParse.substring(questionMarkIndex + 1); 1204 | if (fragmentIndex != -1) { 1205 | fragmentIndex = queryString.indexOf("#"); 1206 | fragmentString = queryString.substring(fragmentIndex + 1); 1207 | queryString = queryString.substring(0, fragmentIndex); 1208 | } 1209 | } else { 1210 | baseUri = uriToParse.substring(0, fragmentIndex); 1211 | fragmentString = uriToParse.substring(fragmentIndex + 1); 1212 | } 1213 | 1214 | return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString }; 1215 | }; 1216 | 1217 | var parseParams = function(paramString) { 1218 | var result = {}; 1219 | var params = paramString.split('&'); 1220 | for (var i = 0; i < params.length; i++) { 1221 | var p = params[i].split('='); 1222 | var paramName = decodeURIComponent(p[0]); 1223 | var paramValue = decodeURIComponent(p[1]); 1224 | result[paramName] = paramValue; 1225 | } 1226 | return result; 1227 | }; 1228 | 1229 | var handleQueryParam = function(paramName, paramValue, oauth) { 1230 | var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ]; 1231 | 1232 | for (var i = 0 ; i< supportedOAuthParams.length ; i++) { 1233 | if (paramName === supportedOAuthParams[i]) { 1234 | oauth[paramName] = paramValue; 1235 | return true; 1236 | } 1237 | } 1238 | return false; 1239 | }; 1240 | 1241 | 1242 | parser.parseUri = function() { 1243 | var parsedUri = initialParse(); 1244 | 1245 | var queryParams = {}; 1246 | if (parsedUri.queryString) { 1247 | queryParams = parseParams(parsedUri.queryString); 1248 | } 1249 | 1250 | var oauth = { newUrl: parsedUri.baseUri }; 1251 | for (var param in queryParams) { 1252 | switch (param) { 1253 | case 'redirect_fragment': 1254 | oauth.fragment = queryParams[param]; 1255 | break; 1256 | default: 1257 | if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { 1258 | oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); 1259 | } 1260 | break; 1261 | } 1262 | } 1263 | 1264 | if (responseMode === 'fragment') { 1265 | var fragmentParams = {}; 1266 | if (parsedUri.fragmentString) { 1267 | fragmentParams = parseParams(parsedUri.fragmentString); 1268 | } 1269 | for (var param in fragmentParams) { 1270 | oauth[param] = fragmentParams[param]; 1271 | } 1272 | } 1273 | 1274 | return oauth; 1275 | }; 1276 | }; 1277 | 1278 | }; 1279 | 1280 | if ( 'object' === "object" && module && 'object' === "object" ) { 1281 | module.exports = Keycloak; 1282 | } else { 1283 | window.Keycloak = Keycloak; 1284 | 1285 | if ( typeof undefined === "function" && undefined.amd ) { 1286 | undefined( "keycloak", [], function () { return Keycloak; } ); 1287 | } 1288 | } 1289 | })( window ); 1290 | }); 1291 | 1292 | /* */ 1293 | var installed = false; 1294 | 1295 | var VueKeyCloak = function VueKeyCloak () {}; 1296 | 1297 | VueKeyCloak.install = function (Vue, options) { 1298 | if ( options === void 0 ) options = { 1299 | keycloakOptions: {}, 1300 | keycloakInitOptions: {}, 1301 | refreshTime: 10 1302 | }; 1303 | 1304 | if (installed) { return } 1305 | installed = true; 1306 | 1307 | var keycloak$$1 = keycloak(options.keycloakOptions); 1308 | 1309 | var watch = new Vue({ 1310 | data: function data () { 1311 | return { 1312 | ready: false, 1313 | authenticated: false, 1314 | user: null, 1315 | token: null, 1316 | resourceAccess: null 1317 | } 1318 | } 1319 | }); 1320 | 1321 | keycloak$$1.init(options.keycloakInitOptions).success(function (isAuthenticated) { 1322 | updateWatchVariables(isAuthenticated).then(function () { 1323 | watch.ready = true; 1324 | }); 1325 | 1326 | if (isAuthenticated) { 1327 | setInterval(function () { 1328 | keycloak$$1.updateToken(options.refreshTime + 2) 1329 | .success(function (refreshed) { 1330 | if (refreshed) { updateWatchVariables(true); } 1331 | }); 1332 | }, options.refreshTime * 1000); 1333 | } 1334 | }); 1335 | 1336 | function updateWatchVariables (isAuthenticated) { 1337 | if ( isAuthenticated === void 0 ) isAuthenticated = false; 1338 | 1339 | watch.authenticated = isAuthenticated; 1340 | 1341 | if (isAuthenticated) { 1342 | watch.token = keycloak$$1.token; 1343 | watch.resourceAccess = keycloak$$1.resourceAccess; 1344 | return new Promise(function (resolve, reject) { 1345 | keycloak$$1.loadUserProfile().success(function (user) { 1346 | watch.user = user; 1347 | resolve(); 1348 | }); 1349 | }) 1350 | } else { 1351 | return Promise.resolve() 1352 | } 1353 | } 1354 | 1355 | Object.defineProperty(Vue.prototype, '$keycloak', { 1356 | get: function get () { 1357 | keycloak$$1.ready = watch.ready; 1358 | keycloak$$1.user = watch.user; 1359 | return keycloak$$1 1360 | } 1361 | }); 1362 | }; 1363 | 1364 | VueKeyCloak.version = '0.0.11'; 1365 | 1366 | export default VueKeyCloak; 1367 | -------------------------------------------------------------------------------- /dist/vue-keycloak.common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vue-keycloak v0.0.11 3 | * (c) Cristian Baldi 2018 4 | */ 5 | 'use strict'; 6 | 7 | function createCommonjsModule(fn, module) { 8 | return module = { exports: {} }, fn(module, module.exports), module.exports; 9 | } 10 | 11 | var keycloak = createCommonjsModule(function (module) { 12 | /* 13 | * Copyright 2016 Red Hat, Inc. and/or its affiliates 14 | * and other contributors as indicated by the @author tags. 15 | * 16 | * Licensed under the Apache License, Version 2.0 (the "License"); 17 | * you may not use this file except in compliance with the License. 18 | * You may obtain a copy of the License at 19 | * 20 | * http://www.apache.org/licenses/LICENSE-2.0 21 | * 22 | * Unless required by applicable law or agreed to in writing, software 23 | * distributed under the License is distributed on an "AS IS" BASIS, 24 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | * See the License for the specific language governing permissions and 26 | * limitations under the License. 27 | */ 28 | 29 | (function( window, undefined ) { 30 | 31 | var Keycloak = function (config) { 32 | if (!(this instanceof Keycloak)) { 33 | return new Keycloak(config); 34 | } 35 | 36 | var kc = this; 37 | var adapter; 38 | var refreshQueue = []; 39 | var callbackStorage; 40 | 41 | var loginIframe = { 42 | enable: true, 43 | callbackList: [], 44 | interval: 5 45 | }; 46 | 47 | var scripts = document.getElementsByTagName('script'); 48 | for (var i = 0; i < scripts.length; i++) { 49 | if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) { 50 | kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0]; 51 | } 52 | } 53 | 54 | kc.init = function (initOptions) { 55 | kc.authenticated = false; 56 | 57 | callbackStorage = createCallbackStorage(); 58 | 59 | if (initOptions && initOptions.adapter === 'cordova') { 60 | adapter = loadAdapter('cordova'); 61 | } else if (initOptions && initOptions.adapter === 'default') { 62 | adapter = loadAdapter(); 63 | } else { 64 | if (window.Cordova || window.cordova) { 65 | adapter = loadAdapter('cordova'); 66 | } else { 67 | adapter = loadAdapter(); 68 | } 69 | } 70 | 71 | if (initOptions) { 72 | if (typeof initOptions.checkLoginIframe !== 'undefined') { 73 | loginIframe.enable = initOptions.checkLoginIframe; 74 | } 75 | 76 | if (initOptions.checkLoginIframeInterval) { 77 | loginIframe.interval = initOptions.checkLoginIframeInterval; 78 | } 79 | 80 | if (initOptions.onLoad === 'login-required') { 81 | kc.loginRequired = true; 82 | } 83 | 84 | if (initOptions.responseMode) { 85 | if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') { 86 | kc.responseMode = initOptions.responseMode; 87 | } else { 88 | throw 'Invalid value for responseMode'; 89 | } 90 | } 91 | 92 | if (initOptions.flow) { 93 | switch (initOptions.flow) { 94 | case 'standard': 95 | kc.responseType = 'code'; 96 | break; 97 | case 'implicit': 98 | kc.responseType = 'id_token token'; 99 | break; 100 | case 'hybrid': 101 | kc.responseType = 'code id_token token'; 102 | break; 103 | default: 104 | throw 'Invalid value for flow'; 105 | } 106 | kc.flow = initOptions.flow; 107 | } 108 | 109 | if (initOptions.timeSkew != null) { 110 | kc.timeSkew = initOptions.timeSkew; 111 | } 112 | } 113 | 114 | if (!kc.responseMode) { 115 | kc.responseMode = 'fragment'; 116 | } 117 | if (!kc.responseType) { 118 | kc.responseType = 'code'; 119 | kc.flow = 'standard'; 120 | } 121 | 122 | var promise = createPromise(); 123 | 124 | var initPromise = createPromise(); 125 | initPromise.promise.success(function() { 126 | kc.onReady && kc.onReady(kc.authenticated); 127 | promise.setSuccess(kc.authenticated); 128 | }).error(function(errorData) { 129 | promise.setError(errorData); 130 | }); 131 | 132 | var configPromise = loadConfig(config); 133 | 134 | function onLoad() { 135 | var doLogin = function(prompt) { 136 | if (!prompt) { 137 | options.prompt = 'none'; 138 | } 139 | kc.login(options).success(function () { 140 | initPromise.setSuccess(); 141 | }).error(function () { 142 | initPromise.setError(); 143 | }); 144 | }; 145 | 146 | var options = {}; 147 | switch (initOptions.onLoad) { 148 | case 'check-sso': 149 | if (loginIframe.enable) { 150 | setupCheckLoginIframe().success(function() { 151 | checkLoginIframe().success(function () { 152 | doLogin(false); 153 | }).error(function () { 154 | initPromise.setSuccess(); 155 | }); 156 | }); 157 | } else { 158 | doLogin(false); 159 | } 160 | break; 161 | case 'login-required': 162 | doLogin(true); 163 | break; 164 | default: 165 | throw 'Invalid value for onLoad'; 166 | } 167 | } 168 | 169 | function processInit() { 170 | var callback = parseCallback(window.location.href); 171 | 172 | if (callback) { 173 | return setupCheckLoginIframe().success(function() { 174 | window.history.replaceState({}, null, callback.newUrl); 175 | processCallback(callback, initPromise); 176 | }).error(function (e) { 177 | initPromise.setError(); 178 | }); 179 | } else if (initOptions) { 180 | if (initOptions.token && initOptions.refreshToken) { 181 | setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); 182 | 183 | if (loginIframe.enable) { 184 | setupCheckLoginIframe().success(function() { 185 | checkLoginIframe().success(function () { 186 | kc.onAuthSuccess && kc.onAuthSuccess(); 187 | initPromise.setSuccess(); 188 | }).error(function () { 189 | setToken(null, null, null); 190 | initPromise.setSuccess(); 191 | }); 192 | }); 193 | } else { 194 | kc.updateToken(-1).success(function() { 195 | kc.onAuthSuccess && kc.onAuthSuccess(); 196 | initPromise.setSuccess(); 197 | }).error(function() { 198 | kc.onAuthError && kc.onAuthError(); 199 | if (initOptions.onLoad) { 200 | onLoad(); 201 | } else { 202 | initPromise.setError(); 203 | } 204 | }); 205 | } 206 | } else if (initOptions.onLoad) { 207 | onLoad(); 208 | } else { 209 | initPromise.setSuccess(); 210 | } 211 | } else { 212 | initPromise.setSuccess(); 213 | } 214 | } 215 | 216 | configPromise.success(processInit); 217 | configPromise.error(function() { 218 | promise.setError(); 219 | }); 220 | 221 | return promise.promise; 222 | }; 223 | 224 | kc.login = function (options) { 225 | return adapter.login(options); 226 | }; 227 | 228 | kc.createLoginUrl = function(options) { 229 | var state = createUUID(); 230 | var nonce = createUUID(); 231 | 232 | var redirectUri = adapter.redirectUri(options); 233 | 234 | var callbackState = { 235 | state: state, 236 | nonce: nonce, 237 | redirectUri: encodeURIComponent(redirectUri) 238 | }; 239 | 240 | if (options && options.prompt) { 241 | callbackState.prompt = options.prompt; 242 | } 243 | 244 | callbackStorage.add(callbackState); 245 | 246 | var action = 'auth'; 247 | if (options && options.action == 'register') { 248 | action = 'registrations'; 249 | } 250 | 251 | var scope = (options && options.scope) ? "openid " + options.scope : "openid"; 252 | 253 | var url = getRealmUrl() 254 | + '/protocol/openid-connect/' + action 255 | + '?client_id=' + encodeURIComponent(kc.clientId) 256 | + '&redirect_uri=' + encodeURIComponent(redirectUri) 257 | + '&state=' + encodeURIComponent(state) 258 | + '&nonce=' + encodeURIComponent(nonce) 259 | + '&response_mode=' + encodeURIComponent(kc.responseMode) 260 | + '&response_type=' + encodeURIComponent(kc.responseType) 261 | + '&scope=' + encodeURIComponent(scope); 262 | 263 | if (options && options.prompt) { 264 | url += '&prompt=' + encodeURIComponent(options.prompt); 265 | } 266 | 267 | if (options && options.maxAge) { 268 | url += '&max_age=' + encodeURIComponent(options.maxAge); 269 | } 270 | 271 | if (options && options.loginHint) { 272 | url += '&login_hint=' + encodeURIComponent(options.loginHint); 273 | } 274 | 275 | if (options && options.idpHint) { 276 | url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint); 277 | } 278 | 279 | if (options && options.locale) { 280 | url += '&ui_locales=' + encodeURIComponent(options.locale); 281 | } 282 | 283 | return url; 284 | }; 285 | 286 | kc.logout = function(options) { 287 | return adapter.logout(options); 288 | }; 289 | 290 | kc.createLogoutUrl = function(options) { 291 | var url = getRealmUrl() 292 | + '/protocol/openid-connect/logout' 293 | + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false)); 294 | 295 | return url; 296 | }; 297 | 298 | kc.register = function (options) { 299 | return adapter.register(options); 300 | }; 301 | 302 | kc.createRegisterUrl = function(options) { 303 | if (!options) { 304 | options = {}; 305 | } 306 | options.action = 'register'; 307 | return kc.createLoginUrl(options); 308 | }; 309 | 310 | kc.createAccountUrl = function(options) { 311 | var url = getRealmUrl() 312 | + '/account' 313 | + '?referrer=' + encodeURIComponent(kc.clientId) 314 | + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options)); 315 | 316 | return url; 317 | }; 318 | 319 | kc.accountManagement = function() { 320 | return adapter.accountManagement(); 321 | }; 322 | 323 | kc.hasRealmRole = function (role) { 324 | var access = kc.realmAccess; 325 | return !!access && access.roles.indexOf(role) >= 0; 326 | }; 327 | 328 | kc.hasResourceRole = function(role, resource) { 329 | if (!kc.resourceAccess) { 330 | return false; 331 | } 332 | 333 | var access = kc.resourceAccess[resource || kc.clientId]; 334 | return !!access && access.roles.indexOf(role) >= 0; 335 | }; 336 | 337 | kc.loadUserProfile = function() { 338 | var url = getRealmUrl() + '/account'; 339 | var req = new XMLHttpRequest(); 340 | req.open('GET', url, true); 341 | req.setRequestHeader('Accept', 'application/json'); 342 | req.setRequestHeader('Authorization', 'bearer ' + kc.token); 343 | 344 | var promise = createPromise(); 345 | 346 | req.onreadystatechange = function () { 347 | if (req.readyState == 4) { 348 | if (req.status == 200) { 349 | kc.profile = JSON.parse(req.responseText); 350 | promise.setSuccess(kc.profile); 351 | } else { 352 | promise.setError(); 353 | } 354 | } 355 | }; 356 | 357 | req.send(); 358 | 359 | return promise.promise; 360 | }; 361 | 362 | kc.loadUserInfo = function() { 363 | var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; 364 | var req = new XMLHttpRequest(); 365 | req.open('GET', url, true); 366 | req.setRequestHeader('Accept', 'application/json'); 367 | req.setRequestHeader('Authorization', 'bearer ' + kc.token); 368 | 369 | var promise = createPromise(); 370 | 371 | req.onreadystatechange = function () { 372 | if (req.readyState == 4) { 373 | if (req.status == 200) { 374 | kc.userInfo = JSON.parse(req.responseText); 375 | promise.setSuccess(kc.userInfo); 376 | } else { 377 | promise.setError(); 378 | } 379 | } 380 | }; 381 | 382 | req.send(); 383 | 384 | return promise.promise; 385 | }; 386 | 387 | kc.isTokenExpired = function(minValidity) { 388 | if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) { 389 | throw 'Not authenticated'; 390 | } 391 | 392 | if (kc.timeSkew == null) { 393 | console.info('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set'); 394 | return true; 395 | } 396 | 397 | var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew; 398 | if (minValidity) { 399 | expiresIn -= minValidity; 400 | } 401 | return expiresIn < 0; 402 | }; 403 | 404 | kc.updateToken = function(minValidity) { 405 | var promise = createPromise(); 406 | 407 | if (!kc.refreshToken) { 408 | promise.setError(); 409 | return promise.promise; 410 | } 411 | 412 | minValidity = minValidity || 5; 413 | 414 | var exec = function() { 415 | var refreshToken = false; 416 | if (minValidity == -1) { 417 | refreshToken = true; 418 | console.info('[KEYCLOAK] Refreshing token: forced refresh'); 419 | } else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) { 420 | refreshToken = true; 421 | console.info('[KEYCLOAK] Refreshing token: token expired'); 422 | } 423 | 424 | if (!refreshToken) { 425 | promise.setSuccess(false); 426 | } else { 427 | var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; 428 | var url = getRealmUrl() + '/protocol/openid-connect/token'; 429 | 430 | refreshQueue.push(promise); 431 | 432 | if (refreshQueue.length == 1) { 433 | var req = new XMLHttpRequest(); 434 | req.open('POST', url, true); 435 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 436 | req.withCredentials = true; 437 | 438 | if (kc.clientId && kc.clientSecret) { 439 | req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); 440 | } else { 441 | params += '&client_id=' + encodeURIComponent(kc.clientId); 442 | } 443 | 444 | var timeLocal = new Date().getTime(); 445 | 446 | req.onreadystatechange = function () { 447 | if (req.readyState == 4) { 448 | if (req.status == 200) { 449 | console.info('[KEYCLOAK] Token refreshed'); 450 | 451 | timeLocal = (timeLocal + new Date().getTime()) / 2; 452 | 453 | var tokenResponse = JSON.parse(req.responseText); 454 | 455 | setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal); 456 | 457 | kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); 458 | for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { 459 | p.setSuccess(true); 460 | } 461 | } else { 462 | console.warn('[KEYCLOAK] Failed to refresh token'); 463 | 464 | kc.onAuthRefreshError && kc.onAuthRefreshError(); 465 | for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { 466 | p.setError(true); 467 | } 468 | } 469 | } 470 | }; 471 | 472 | req.send(params); 473 | } 474 | } 475 | }; 476 | 477 | if (loginIframe.enable) { 478 | var iframePromise = checkLoginIframe(); 479 | iframePromise.success(function() { 480 | exec(); 481 | }).error(function() { 482 | promise.setError(); 483 | }); 484 | } else { 485 | exec(); 486 | } 487 | 488 | return promise.promise; 489 | }; 490 | 491 | kc.clearToken = function() { 492 | if (kc.token) { 493 | setToken(null, null, null); 494 | kc.onAuthLogout && kc.onAuthLogout(); 495 | if (kc.loginRequired) { 496 | kc.login(); 497 | } 498 | } 499 | }; 500 | 501 | function getRealmUrl() { 502 | if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { 503 | return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); 504 | } else { 505 | return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); 506 | } 507 | } 508 | 509 | function getOrigin() { 510 | if (!window.location.origin) { 511 | return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); 512 | } else { 513 | return window.location.origin; 514 | } 515 | } 516 | 517 | function processCallback(oauth, promise) { 518 | var code = oauth.code; 519 | var error = oauth.error; 520 | var prompt = oauth.prompt; 521 | 522 | var timeLocal = new Date().getTime(); 523 | 524 | if (error) { 525 | if (prompt != 'none') { 526 | var errorData = { error: error, error_description: oauth.error_description }; 527 | kc.onAuthError && kc.onAuthError(errorData); 528 | promise && promise.setError(errorData); 529 | } else { 530 | promise && promise.setSuccess(); 531 | } 532 | return; 533 | } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) { 534 | authSuccess(oauth.access_token, null, oauth.id_token, true); 535 | } 536 | 537 | if ((kc.flow != 'implicit') && code) { 538 | var params = 'code=' + code + '&grant_type=authorization_code'; 539 | var url = getRealmUrl() + '/protocol/openid-connect/token'; 540 | 541 | var req = new XMLHttpRequest(); 542 | req.open('POST', url, true); 543 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 544 | 545 | if (kc.clientId && kc.clientSecret) { 546 | req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); 547 | } else { 548 | params += '&client_id=' + encodeURIComponent(kc.clientId); 549 | } 550 | 551 | params += '&redirect_uri=' + oauth.redirectUri; 552 | 553 | req.withCredentials = true; 554 | 555 | req.onreadystatechange = function() { 556 | if (req.readyState == 4) { 557 | if (req.status == 200) { 558 | 559 | var tokenResponse = JSON.parse(req.responseText); 560 | authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard'); 561 | } else { 562 | kc.onAuthError && kc.onAuthError(); 563 | promise && promise.setError(); 564 | } 565 | } 566 | }; 567 | 568 | req.send(params); 569 | } 570 | 571 | function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) { 572 | timeLocal = (timeLocal + new Date().getTime()) / 2; 573 | 574 | setToken(accessToken, refreshToken, idToken, timeLocal); 575 | 576 | if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || 577 | (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) || 578 | (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) { 579 | 580 | console.info('[KEYCLOAK] Invalid nonce, clearing token'); 581 | kc.clearToken(); 582 | promise && promise.setError(); 583 | } else { 584 | if (fulfillPromise) { 585 | kc.onAuthSuccess && kc.onAuthSuccess(); 586 | promise && promise.setSuccess(); 587 | } 588 | } 589 | } 590 | 591 | } 592 | 593 | function loadConfig(url) { 594 | var promise = createPromise(); 595 | var configUrl; 596 | 597 | if (!config) { 598 | configUrl = 'keycloak.json'; 599 | } else if (typeof config === 'string') { 600 | configUrl = config; 601 | } 602 | 603 | if (configUrl) { 604 | var req = new XMLHttpRequest(); 605 | req.open('GET', configUrl, true); 606 | req.setRequestHeader('Accept', 'application/json'); 607 | 608 | req.onreadystatechange = function () { 609 | if (req.readyState == 4) { 610 | if (req.status == 200 || fileLoaded(req)) { 611 | var config = JSON.parse(req.responseText); 612 | 613 | kc.authServerUrl = config['auth-server-url']; 614 | kc.realm = config['realm']; 615 | kc.clientId = config['resource']; 616 | kc.clientSecret = (config['credentials'] || {})['secret']; 617 | 618 | promise.setSuccess(); 619 | } else { 620 | promise.setError(); 621 | } 622 | } 623 | }; 624 | 625 | req.send(); 626 | } else { 627 | if (!config['url']) { 628 | var scripts = document.getElementsByTagName('script'); 629 | for (var i = 0; i < scripts.length; i++) { 630 | if (scripts[i].src.match(/.*keycloak\.js/)) { 631 | config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); 632 | break; 633 | } 634 | } 635 | } 636 | 637 | if (!config.realm) { 638 | throw 'realm missing'; 639 | } 640 | 641 | if (!config.clientId) { 642 | throw 'clientId missing'; 643 | } 644 | 645 | kc.authServerUrl = config.url; 646 | kc.realm = config.realm; 647 | kc.clientId = config.clientId; 648 | kc.clientSecret = (config.credentials || {}).secret; 649 | 650 | promise.setSuccess(); 651 | } 652 | 653 | return promise.promise; 654 | } 655 | 656 | function fileLoaded(xhr) { 657 | return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:'); 658 | } 659 | 660 | function setToken(token, refreshToken, idToken, timeLocal) { 661 | if (kc.tokenTimeoutHandle) { 662 | clearTimeout(kc.tokenTimeoutHandle); 663 | kc.tokenTimeoutHandle = null; 664 | } 665 | 666 | if (refreshToken) { 667 | kc.refreshToken = refreshToken; 668 | kc.refreshTokenParsed = decodeToken(refreshToken); 669 | } else { 670 | delete kc.refreshToken; 671 | delete kc.refreshTokenParsed; 672 | } 673 | 674 | if (idToken) { 675 | kc.idToken = idToken; 676 | kc.idTokenParsed = decodeToken(idToken); 677 | } else { 678 | delete kc.idToken; 679 | delete kc.idTokenParsed; 680 | } 681 | 682 | if (token) { 683 | kc.token = token; 684 | kc.tokenParsed = decodeToken(token); 685 | kc.sessionId = kc.tokenParsed.session_state; 686 | kc.authenticated = true; 687 | kc.subject = kc.tokenParsed.sub; 688 | kc.realmAccess = kc.tokenParsed.realm_access; 689 | kc.resourceAccess = kc.tokenParsed.resource_access; 690 | 691 | if (timeLocal) { 692 | kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; 693 | } 694 | 695 | if (kc.timeSkew != null) { 696 | console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds'); 697 | 698 | if (kc.onTokenExpired) { 699 | var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000; 700 | console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s'); 701 | if (expiresIn <= 0) { 702 | kc.onTokenExpired(); 703 | } else { 704 | kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn); 705 | } 706 | } 707 | } 708 | } else { 709 | delete kc.token; 710 | delete kc.tokenParsed; 711 | delete kc.subject; 712 | delete kc.realmAccess; 713 | delete kc.resourceAccess; 714 | 715 | kc.authenticated = false; 716 | } 717 | } 718 | 719 | function decodeToken(str) { 720 | str = str.split('.')[1]; 721 | 722 | str = str.replace('/-/g', '+'); 723 | str = str.replace('/_/g', '/'); 724 | switch (str.length % 4) 725 | { 726 | case 0: 727 | break; 728 | case 2: 729 | str += '=='; 730 | break; 731 | case 3: 732 | str += '='; 733 | break; 734 | default: 735 | throw 'Invalid token'; 736 | } 737 | 738 | str = (str + '===').slice(0, str.length + (str.length % 4)); 739 | str = str.replace(/-/g, '+').replace(/_/g, '/'); 740 | 741 | str = decodeURIComponent(escape(atob(str))); 742 | 743 | str = JSON.parse(str); 744 | return str; 745 | } 746 | 747 | function createUUID() { 748 | var s = []; 749 | var hexDigits = '0123456789abcdef'; 750 | for (var i = 0; i < 36; i++) { 751 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); 752 | } 753 | s[14] = '4'; 754 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); 755 | s[8] = s[13] = s[18] = s[23] = '-'; 756 | var uuid = s.join(''); 757 | return uuid; 758 | } 759 | 760 | kc.callback_id = 0; 761 | 762 | function parseCallback(url) { 763 | var oauth = new CallbackParser(url, kc.responseMode).parseUri(); 764 | var oauthState = callbackStorage.get(oauth.state); 765 | 766 | if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) { 767 | oauth.redirectUri = oauthState.redirectUri; 768 | oauth.storedNonce = oauthState.nonce; 769 | oauth.prompt = oauthState.prompt; 770 | 771 | if (oauth.fragment) { 772 | oauth.newUrl += '#' + oauth.fragment; 773 | } 774 | 775 | return oauth; 776 | } 777 | } 778 | 779 | function createPromise() { 780 | var p = { 781 | setSuccess: function(result) { 782 | p.success = true; 783 | p.result = result; 784 | if (p.successCallback) { 785 | p.successCallback(result); 786 | } 787 | }, 788 | 789 | setError: function(result) { 790 | p.error = true; 791 | p.result = result; 792 | if (p.errorCallback) { 793 | p.errorCallback(result); 794 | } 795 | }, 796 | 797 | promise: { 798 | success: function(callback) { 799 | if (p.success) { 800 | callback(p.result); 801 | } else if (!p.error) { 802 | p.successCallback = callback; 803 | } 804 | return p.promise; 805 | }, 806 | error: function(callback) { 807 | if (p.error) { 808 | callback(p.result); 809 | } else if (!p.success) { 810 | p.errorCallback = callback; 811 | } 812 | return p.promise; 813 | } 814 | } 815 | }; 816 | return p; 817 | } 818 | 819 | function setupCheckLoginIframe() { 820 | var promise = createPromise(); 821 | 822 | if (!loginIframe.enable) { 823 | promise.setSuccess(); 824 | return promise.promise; 825 | } 826 | 827 | if (loginIframe.iframe) { 828 | promise.setSuccess(); 829 | return promise.promise; 830 | } 831 | 832 | var iframe = document.createElement('iframe'); 833 | loginIframe.iframe = iframe; 834 | 835 | iframe.onload = function() { 836 | var realmUrl = getRealmUrl(); 837 | if (realmUrl.charAt(0) === '/') { 838 | loginIframe.iframeOrigin = getOrigin(); 839 | } else { 840 | loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); 841 | } 842 | promise.setSuccess(); 843 | 844 | setTimeout(check, loginIframe.interval * 1000); 845 | }; 846 | 847 | var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; 848 | if (kc.iframeVersion) { 849 | src = src + '?version=' + kc.iframeVersion; 850 | } 851 | 852 | iframe.setAttribute('src', src ); 853 | iframe.setAttribute('title', 'keycloak-session-iframe' ); 854 | iframe.style.display = 'none'; 855 | document.body.appendChild(iframe); 856 | 857 | var messageCallback = function(event) { 858 | if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) { 859 | return; 860 | } 861 | 862 | if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) { 863 | return; 864 | } 865 | 866 | 867 | if (event.data != 'unchanged') { 868 | kc.clearToken(); 869 | } 870 | 871 | var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length); 872 | 873 | for (var i = callbacks.length - 1; i >= 0; --i) { 874 | var promise = callbacks[i]; 875 | if (event.data == 'unchanged') { 876 | promise.setSuccess(); 877 | } else { 878 | promise.setError(); 879 | } 880 | } 881 | }; 882 | 883 | window.addEventListener('message', messageCallback, false); 884 | 885 | var check = function() { 886 | checkLoginIframe(); 887 | if (kc.token) { 888 | setTimeout(check, loginIframe.interval * 1000); 889 | } 890 | }; 891 | 892 | return promise.promise; 893 | } 894 | 895 | function checkLoginIframe() { 896 | var promise = createPromise(); 897 | 898 | if (loginIframe.iframe && loginIframe.iframeOrigin ) { 899 | var msg = kc.clientId + ' ' + kc.sessionId; 900 | loginIframe.callbackList.push(promise); 901 | var origin = loginIframe.iframeOrigin; 902 | if (loginIframe.callbackList.length == 1) { 903 | loginIframe.iframe.contentWindow.postMessage(msg, origin); 904 | } 905 | } else { 906 | promise.setSuccess(); 907 | } 908 | 909 | return promise.promise; 910 | } 911 | 912 | function loadAdapter(type) { 913 | if (!type || type == 'default') { 914 | return { 915 | login: function(options) { 916 | window.location.href = kc.createLoginUrl(options); 917 | return createPromise().promise; 918 | }, 919 | 920 | logout: function(options) { 921 | window.location.href = kc.createLogoutUrl(options); 922 | return createPromise().promise; 923 | }, 924 | 925 | register: function(options) { 926 | window.location.href = kc.createRegisterUrl(options); 927 | return createPromise().promise; 928 | }, 929 | 930 | accountManagement : function() { 931 | window.location.href = kc.createAccountUrl(); 932 | return createPromise().promise; 933 | }, 934 | 935 | redirectUri: function(options, encodeHash) { 936 | if (arguments.length == 1) { 937 | encodeHash = true; 938 | } 939 | 940 | if (options && options.redirectUri) { 941 | return options.redirectUri; 942 | } else if (kc.redirectUri) { 943 | return kc.redirectUri; 944 | } else { 945 | var redirectUri = location.href; 946 | if (location.hash && encodeHash) { 947 | redirectUri = redirectUri.substring(0, location.href.indexOf('#')); 948 | redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); 949 | } 950 | return redirectUri; 951 | } 952 | } 953 | }; 954 | } 955 | 956 | if (type == 'cordova') { 957 | loginIframe.enable = false; 958 | var cordovaOpenWindowWrapper = function(loginUrl, target, options) { 959 | if (window.cordova && window.cordova.InAppBrowser) { 960 | // Use inappbrowser for IOS and Android if available 961 | return window.cordova.InAppBrowser.open(loginUrl, target, options); 962 | } else { 963 | return window.open(loginUrl, target, options); 964 | } 965 | }; 966 | return { 967 | login: function(options) { 968 | var promise = createPromise(); 969 | 970 | var o = 'location=no'; 971 | if (options && options.prompt == 'none') { 972 | o += ',hidden=yes'; 973 | } 974 | 975 | var loginUrl = kc.createLoginUrl(options); 976 | var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o); 977 | var completed = false; 978 | 979 | ref.addEventListener('loadstart', function(event) { 980 | if (event.url.indexOf('http://localhost') == 0) { 981 | var callback = parseCallback(event.url); 982 | processCallback(callback, promise); 983 | ref.close(); 984 | completed = true; 985 | } 986 | }); 987 | 988 | ref.addEventListener('loaderror', function(event) { 989 | if (!completed) { 990 | if (event.url.indexOf('http://localhost') == 0) { 991 | var callback = parseCallback(event.url); 992 | processCallback(callback, promise); 993 | ref.close(); 994 | completed = true; 995 | } else { 996 | promise.setError(); 997 | ref.close(); 998 | } 999 | } 1000 | }); 1001 | 1002 | return promise.promise; 1003 | }, 1004 | 1005 | logout: function(options) { 1006 | var promise = createPromise(); 1007 | 1008 | var logoutUrl = kc.createLogoutUrl(options); 1009 | var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); 1010 | 1011 | var error; 1012 | 1013 | ref.addEventListener('loadstart', function(event) { 1014 | if (event.url.indexOf('http://localhost') == 0) { 1015 | ref.close(); 1016 | } 1017 | }); 1018 | 1019 | ref.addEventListener('loaderror', function(event) { 1020 | if (event.url.indexOf('http://localhost') == 0) { 1021 | ref.close(); 1022 | } else { 1023 | error = true; 1024 | ref.close(); 1025 | } 1026 | }); 1027 | 1028 | ref.addEventListener('exit', function(event) { 1029 | if (error) { 1030 | promise.setError(); 1031 | } else { 1032 | kc.clearToken(); 1033 | promise.setSuccess(); 1034 | } 1035 | }); 1036 | 1037 | return promise.promise; 1038 | }, 1039 | 1040 | register : function() { 1041 | var registerUrl = kc.createRegisterUrl(); 1042 | var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no'); 1043 | ref.addEventListener('loadstart', function(event) { 1044 | if (event.url.indexOf('http://localhost') == 0) { 1045 | ref.close(); 1046 | } 1047 | }); 1048 | }, 1049 | 1050 | accountManagement : function() { 1051 | var accountUrl = kc.createAccountUrl(); 1052 | var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); 1053 | ref.addEventListener('loadstart', function(event) { 1054 | if (event.url.indexOf('http://localhost') == 0) { 1055 | ref.close(); 1056 | } 1057 | }); 1058 | }, 1059 | 1060 | redirectUri: function(options) { 1061 | return 'http://localhost'; 1062 | } 1063 | } 1064 | } 1065 | 1066 | throw 'invalid adapter type: ' + type; 1067 | } 1068 | 1069 | var LocalStorage = function() { 1070 | if (!(this instanceof LocalStorage)) { 1071 | return new LocalStorage(); 1072 | } 1073 | 1074 | localStorage.setItem('kc-test', 'test'); 1075 | localStorage.removeItem('kc-test'); 1076 | 1077 | var cs = this; 1078 | 1079 | function clearExpired() { 1080 | var time = new Date().getTime(); 1081 | for (var i = 0; i < localStorage.length; i++) { 1082 | var key = localStorage.key(i); 1083 | if (key && key.indexOf('kc-callback-') == 0) { 1084 | var value = localStorage.getItem(key); 1085 | if (value) { 1086 | try { 1087 | var expires = JSON.parse(value).expires; 1088 | if (!expires || expires < time) { 1089 | localStorage.removeItem(key); 1090 | } 1091 | } catch (err) { 1092 | localStorage.removeItem(key); 1093 | } 1094 | } 1095 | } 1096 | } 1097 | } 1098 | 1099 | cs.get = function(state) { 1100 | if (!state) { 1101 | return; 1102 | } 1103 | 1104 | var key = 'kc-callback-' + state; 1105 | var value = localStorage.getItem(key); 1106 | if (value) { 1107 | localStorage.removeItem(key); 1108 | value = JSON.parse(value); 1109 | } 1110 | 1111 | clearExpired(); 1112 | return value; 1113 | }; 1114 | 1115 | cs.add = function(state) { 1116 | clearExpired(); 1117 | 1118 | var key = 'kc-callback-' + state.state; 1119 | state.expires = new Date().getTime() + (60 * 60 * 1000); 1120 | localStorage.setItem(key, JSON.stringify(state)); 1121 | }; 1122 | }; 1123 | 1124 | var CookieStorage = function() { 1125 | if (!(this instanceof CookieStorage)) { 1126 | return new CookieStorage(); 1127 | } 1128 | 1129 | var cs = this; 1130 | 1131 | cs.get = function(state) { 1132 | if (!state) { 1133 | return; 1134 | } 1135 | 1136 | var value = getCookie('kc-callback-' + state); 1137 | setCookie('kc-callback-' + state, '', cookieExpiration(-100)); 1138 | if (value) { 1139 | return JSON.parse(value); 1140 | } 1141 | }; 1142 | 1143 | cs.add = function(state) { 1144 | setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60)); 1145 | }; 1146 | 1147 | cs.removeItem = function(key) { 1148 | setCookie(key, '', cookieExpiration(-100)); 1149 | }; 1150 | 1151 | var cookieExpiration = function (minutes) { 1152 | var exp = new Date(); 1153 | exp.setTime(exp.getTime() + (minutes*60*1000)); 1154 | return exp; 1155 | }; 1156 | 1157 | var getCookie = function (key) { 1158 | var name = key + '='; 1159 | var ca = document.cookie.split(';'); 1160 | for (var i = 0; i < ca.length; i++) { 1161 | var c = ca[i]; 1162 | while (c.charAt(0) == ' ') { 1163 | c = c.substring(1); 1164 | } 1165 | if (c.indexOf(name) == 0) { 1166 | return c.substring(name.length, c.length); 1167 | } 1168 | } 1169 | return ''; 1170 | }; 1171 | 1172 | var setCookie = function (key, value, expirationDate) { 1173 | var cookie = key + '=' + value + '; ' 1174 | + 'expires=' + expirationDate.toUTCString() + '; '; 1175 | document.cookie = cookie; 1176 | }; 1177 | }; 1178 | 1179 | function createCallbackStorage() { 1180 | try { 1181 | return new LocalStorage(); 1182 | } catch (err) { 1183 | } 1184 | 1185 | return new CookieStorage(); 1186 | } 1187 | 1188 | var CallbackParser = function(uriToParse, responseMode) { 1189 | if (!(this instanceof CallbackParser)) { 1190 | return new CallbackParser(uriToParse, responseMode); 1191 | } 1192 | var parser = this; 1193 | 1194 | var initialParse = function() { 1195 | var baseUri = null; 1196 | var queryString = null; 1197 | var fragmentString = null; 1198 | 1199 | var questionMarkIndex = uriToParse.indexOf("?"); 1200 | var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1); 1201 | if (questionMarkIndex == -1 && fragmentIndex == -1) { 1202 | baseUri = uriToParse; 1203 | } else if (questionMarkIndex != -1) { 1204 | baseUri = uriToParse.substring(0, questionMarkIndex); 1205 | queryString = uriToParse.substring(questionMarkIndex + 1); 1206 | if (fragmentIndex != -1) { 1207 | fragmentIndex = queryString.indexOf("#"); 1208 | fragmentString = queryString.substring(fragmentIndex + 1); 1209 | queryString = queryString.substring(0, fragmentIndex); 1210 | } 1211 | } else { 1212 | baseUri = uriToParse.substring(0, fragmentIndex); 1213 | fragmentString = uriToParse.substring(fragmentIndex + 1); 1214 | } 1215 | 1216 | return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString }; 1217 | }; 1218 | 1219 | var parseParams = function(paramString) { 1220 | var result = {}; 1221 | var params = paramString.split('&'); 1222 | for (var i = 0; i < params.length; i++) { 1223 | var p = params[i].split('='); 1224 | var paramName = decodeURIComponent(p[0]); 1225 | var paramValue = decodeURIComponent(p[1]); 1226 | result[paramName] = paramValue; 1227 | } 1228 | return result; 1229 | }; 1230 | 1231 | var handleQueryParam = function(paramName, paramValue, oauth) { 1232 | var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ]; 1233 | 1234 | for (var i = 0 ; i< supportedOAuthParams.length ; i++) { 1235 | if (paramName === supportedOAuthParams[i]) { 1236 | oauth[paramName] = paramValue; 1237 | return true; 1238 | } 1239 | } 1240 | return false; 1241 | }; 1242 | 1243 | 1244 | parser.parseUri = function() { 1245 | var parsedUri = initialParse(); 1246 | 1247 | var queryParams = {}; 1248 | if (parsedUri.queryString) { 1249 | queryParams = parseParams(parsedUri.queryString); 1250 | } 1251 | 1252 | var oauth = { newUrl: parsedUri.baseUri }; 1253 | for (var param in queryParams) { 1254 | switch (param) { 1255 | case 'redirect_fragment': 1256 | oauth.fragment = queryParams[param]; 1257 | break; 1258 | default: 1259 | if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { 1260 | oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); 1261 | } 1262 | break; 1263 | } 1264 | } 1265 | 1266 | if (responseMode === 'fragment') { 1267 | var fragmentParams = {}; 1268 | if (parsedUri.fragmentString) { 1269 | fragmentParams = parseParams(parsedUri.fragmentString); 1270 | } 1271 | for (var param in fragmentParams) { 1272 | oauth[param] = fragmentParams[param]; 1273 | } 1274 | } 1275 | 1276 | return oauth; 1277 | }; 1278 | }; 1279 | 1280 | }; 1281 | 1282 | if ( 'object' === "object" && module && 'object' === "object" ) { 1283 | module.exports = Keycloak; 1284 | } else { 1285 | window.Keycloak = Keycloak; 1286 | 1287 | if ( typeof undefined === "function" && undefined.amd ) { 1288 | undefined( "keycloak", [], function () { return Keycloak; } ); 1289 | } 1290 | } 1291 | })( window ); 1292 | }); 1293 | 1294 | /* */ 1295 | var installed = false; 1296 | 1297 | var VueKeyCloak = function VueKeyCloak () {}; 1298 | 1299 | VueKeyCloak.install = function (Vue, options) { 1300 | if ( options === void 0 ) options = { 1301 | keycloakOptions: {}, 1302 | keycloakInitOptions: {}, 1303 | refreshTime: 10 1304 | }; 1305 | 1306 | if (installed) { return } 1307 | installed = true; 1308 | 1309 | var keycloak$$1 = keycloak(options.keycloakOptions); 1310 | 1311 | var watch = new Vue({ 1312 | data: function data () { 1313 | return { 1314 | ready: false, 1315 | authenticated: false, 1316 | user: null, 1317 | token: null, 1318 | resourceAccess: null 1319 | } 1320 | } 1321 | }); 1322 | 1323 | keycloak$$1.init(options.keycloakInitOptions).success(function (isAuthenticated) { 1324 | updateWatchVariables(isAuthenticated).then(function () { 1325 | watch.ready = true; 1326 | }); 1327 | 1328 | if (isAuthenticated) { 1329 | setInterval(function () { 1330 | keycloak$$1.updateToken(options.refreshTime + 2) 1331 | .success(function (refreshed) { 1332 | if (refreshed) { updateWatchVariables(true); } 1333 | }); 1334 | }, options.refreshTime * 1000); 1335 | } 1336 | }); 1337 | 1338 | function updateWatchVariables (isAuthenticated) { 1339 | if ( isAuthenticated === void 0 ) isAuthenticated = false; 1340 | 1341 | watch.authenticated = isAuthenticated; 1342 | 1343 | if (isAuthenticated) { 1344 | watch.token = keycloak$$1.token; 1345 | watch.resourceAccess = keycloak$$1.resourceAccess; 1346 | return new Promise(function (resolve, reject) { 1347 | keycloak$$1.loadUserProfile().success(function (user) { 1348 | watch.user = user; 1349 | resolve(); 1350 | }); 1351 | }) 1352 | } else { 1353 | return Promise.resolve() 1354 | } 1355 | } 1356 | 1357 | Object.defineProperty(Vue.prototype, '$keycloak', { 1358 | get: function get () { 1359 | keycloak$$1.ready = watch.ready; 1360 | keycloak$$1.user = watch.user; 1361 | return keycloak$$1 1362 | } 1363 | }); 1364 | }; 1365 | 1366 | VueKeyCloak.version = '0.0.11'; 1367 | 1368 | module.exports = VueKeyCloak; 1369 | -------------------------------------------------------------------------------- /dist/vue-keycloak.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vue-keycloak v0.0.11 3 | * (c) Cristian Baldi 2018 4 | */ 5 | (function (global, factory) { 6 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 7 | typeof define === 'function' && define.amd ? define(factory) : 8 | (global.VueKeycloak = factory()); 9 | }(this, (function () { 'use strict'; 10 | 11 | function createCommonjsModule(fn, module) { 12 | return module = { exports: {} }, fn(module, module.exports), module.exports; 13 | } 14 | 15 | var keycloak = createCommonjsModule(function (module) { 16 | /* 17 | * Copyright 2016 Red Hat, Inc. and/or its affiliates 18 | * and other contributors as indicated by the @author tags. 19 | * 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | 33 | (function( window, undefined ) { 34 | 35 | var Keycloak = function (config) { 36 | if (!(this instanceof Keycloak)) { 37 | return new Keycloak(config); 38 | } 39 | 40 | var kc = this; 41 | var adapter; 42 | var refreshQueue = []; 43 | var callbackStorage; 44 | 45 | var loginIframe = { 46 | enable: true, 47 | callbackList: [], 48 | interval: 5 49 | }; 50 | 51 | var scripts = document.getElementsByTagName('script'); 52 | for (var i = 0; i < scripts.length; i++) { 53 | if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) { 54 | kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0]; 55 | } 56 | } 57 | 58 | kc.init = function (initOptions) { 59 | kc.authenticated = false; 60 | 61 | callbackStorage = createCallbackStorage(); 62 | 63 | if (initOptions && initOptions.adapter === 'cordova') { 64 | adapter = loadAdapter('cordova'); 65 | } else if (initOptions && initOptions.adapter === 'default') { 66 | adapter = loadAdapter(); 67 | } else { 68 | if (window.Cordova || window.cordova) { 69 | adapter = loadAdapter('cordova'); 70 | } else { 71 | adapter = loadAdapter(); 72 | } 73 | } 74 | 75 | if (initOptions) { 76 | if (typeof initOptions.checkLoginIframe !== 'undefined') { 77 | loginIframe.enable = initOptions.checkLoginIframe; 78 | } 79 | 80 | if (initOptions.checkLoginIframeInterval) { 81 | loginIframe.interval = initOptions.checkLoginIframeInterval; 82 | } 83 | 84 | if (initOptions.onLoad === 'login-required') { 85 | kc.loginRequired = true; 86 | } 87 | 88 | if (initOptions.responseMode) { 89 | if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') { 90 | kc.responseMode = initOptions.responseMode; 91 | } else { 92 | throw 'Invalid value for responseMode'; 93 | } 94 | } 95 | 96 | if (initOptions.flow) { 97 | switch (initOptions.flow) { 98 | case 'standard': 99 | kc.responseType = 'code'; 100 | break; 101 | case 'implicit': 102 | kc.responseType = 'id_token token'; 103 | break; 104 | case 'hybrid': 105 | kc.responseType = 'code id_token token'; 106 | break; 107 | default: 108 | throw 'Invalid value for flow'; 109 | } 110 | kc.flow = initOptions.flow; 111 | } 112 | 113 | if (initOptions.timeSkew != null) { 114 | kc.timeSkew = initOptions.timeSkew; 115 | } 116 | } 117 | 118 | if (!kc.responseMode) { 119 | kc.responseMode = 'fragment'; 120 | } 121 | if (!kc.responseType) { 122 | kc.responseType = 'code'; 123 | kc.flow = 'standard'; 124 | } 125 | 126 | var promise = createPromise(); 127 | 128 | var initPromise = createPromise(); 129 | initPromise.promise.success(function() { 130 | kc.onReady && kc.onReady(kc.authenticated); 131 | promise.setSuccess(kc.authenticated); 132 | }).error(function(errorData) { 133 | promise.setError(errorData); 134 | }); 135 | 136 | var configPromise = loadConfig(config); 137 | 138 | function onLoad() { 139 | var doLogin = function(prompt) { 140 | if (!prompt) { 141 | options.prompt = 'none'; 142 | } 143 | kc.login(options).success(function () { 144 | initPromise.setSuccess(); 145 | }).error(function () { 146 | initPromise.setError(); 147 | }); 148 | }; 149 | 150 | var options = {}; 151 | switch (initOptions.onLoad) { 152 | case 'check-sso': 153 | if (loginIframe.enable) { 154 | setupCheckLoginIframe().success(function() { 155 | checkLoginIframe().success(function () { 156 | doLogin(false); 157 | }).error(function () { 158 | initPromise.setSuccess(); 159 | }); 160 | }); 161 | } else { 162 | doLogin(false); 163 | } 164 | break; 165 | case 'login-required': 166 | doLogin(true); 167 | break; 168 | default: 169 | throw 'Invalid value for onLoad'; 170 | } 171 | } 172 | 173 | function processInit() { 174 | var callback = parseCallback(window.location.href); 175 | 176 | if (callback) { 177 | return setupCheckLoginIframe().success(function() { 178 | window.history.replaceState({}, null, callback.newUrl); 179 | processCallback(callback, initPromise); 180 | }).error(function (e) { 181 | initPromise.setError(); 182 | }); 183 | } else if (initOptions) { 184 | if (initOptions.token && initOptions.refreshToken) { 185 | setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); 186 | 187 | if (loginIframe.enable) { 188 | setupCheckLoginIframe().success(function() { 189 | checkLoginIframe().success(function () { 190 | kc.onAuthSuccess && kc.onAuthSuccess(); 191 | initPromise.setSuccess(); 192 | }).error(function () { 193 | setToken(null, null, null); 194 | initPromise.setSuccess(); 195 | }); 196 | }); 197 | } else { 198 | kc.updateToken(-1).success(function() { 199 | kc.onAuthSuccess && kc.onAuthSuccess(); 200 | initPromise.setSuccess(); 201 | }).error(function() { 202 | kc.onAuthError && kc.onAuthError(); 203 | if (initOptions.onLoad) { 204 | onLoad(); 205 | } else { 206 | initPromise.setError(); 207 | } 208 | }); 209 | } 210 | } else if (initOptions.onLoad) { 211 | onLoad(); 212 | } else { 213 | initPromise.setSuccess(); 214 | } 215 | } else { 216 | initPromise.setSuccess(); 217 | } 218 | } 219 | 220 | configPromise.success(processInit); 221 | configPromise.error(function() { 222 | promise.setError(); 223 | }); 224 | 225 | return promise.promise; 226 | }; 227 | 228 | kc.login = function (options) { 229 | return adapter.login(options); 230 | }; 231 | 232 | kc.createLoginUrl = function(options) { 233 | var state = createUUID(); 234 | var nonce = createUUID(); 235 | 236 | var redirectUri = adapter.redirectUri(options); 237 | 238 | var callbackState = { 239 | state: state, 240 | nonce: nonce, 241 | redirectUri: encodeURIComponent(redirectUri) 242 | }; 243 | 244 | if (options && options.prompt) { 245 | callbackState.prompt = options.prompt; 246 | } 247 | 248 | callbackStorage.add(callbackState); 249 | 250 | var action = 'auth'; 251 | if (options && options.action == 'register') { 252 | action = 'registrations'; 253 | } 254 | 255 | var scope = (options && options.scope) ? "openid " + options.scope : "openid"; 256 | 257 | var url = getRealmUrl() 258 | + '/protocol/openid-connect/' + action 259 | + '?client_id=' + encodeURIComponent(kc.clientId) 260 | + '&redirect_uri=' + encodeURIComponent(redirectUri) 261 | + '&state=' + encodeURIComponent(state) 262 | + '&nonce=' + encodeURIComponent(nonce) 263 | + '&response_mode=' + encodeURIComponent(kc.responseMode) 264 | + '&response_type=' + encodeURIComponent(kc.responseType) 265 | + '&scope=' + encodeURIComponent(scope); 266 | 267 | if (options && options.prompt) { 268 | url += '&prompt=' + encodeURIComponent(options.prompt); 269 | } 270 | 271 | if (options && options.maxAge) { 272 | url += '&max_age=' + encodeURIComponent(options.maxAge); 273 | } 274 | 275 | if (options && options.loginHint) { 276 | url += '&login_hint=' + encodeURIComponent(options.loginHint); 277 | } 278 | 279 | if (options && options.idpHint) { 280 | url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint); 281 | } 282 | 283 | if (options && options.locale) { 284 | url += '&ui_locales=' + encodeURIComponent(options.locale); 285 | } 286 | 287 | return url; 288 | }; 289 | 290 | kc.logout = function(options) { 291 | return adapter.logout(options); 292 | }; 293 | 294 | kc.createLogoutUrl = function(options) { 295 | var url = getRealmUrl() 296 | + '/protocol/openid-connect/logout' 297 | + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false)); 298 | 299 | return url; 300 | }; 301 | 302 | kc.register = function (options) { 303 | return adapter.register(options); 304 | }; 305 | 306 | kc.createRegisterUrl = function(options) { 307 | if (!options) { 308 | options = {}; 309 | } 310 | options.action = 'register'; 311 | return kc.createLoginUrl(options); 312 | }; 313 | 314 | kc.createAccountUrl = function(options) { 315 | var url = getRealmUrl() 316 | + '/account' 317 | + '?referrer=' + encodeURIComponent(kc.clientId) 318 | + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options)); 319 | 320 | return url; 321 | }; 322 | 323 | kc.accountManagement = function() { 324 | return adapter.accountManagement(); 325 | }; 326 | 327 | kc.hasRealmRole = function (role) { 328 | var access = kc.realmAccess; 329 | return !!access && access.roles.indexOf(role) >= 0; 330 | }; 331 | 332 | kc.hasResourceRole = function(role, resource) { 333 | if (!kc.resourceAccess) { 334 | return false; 335 | } 336 | 337 | var access = kc.resourceAccess[resource || kc.clientId]; 338 | return !!access && access.roles.indexOf(role) >= 0; 339 | }; 340 | 341 | kc.loadUserProfile = function() { 342 | var url = getRealmUrl() + '/account'; 343 | var req = new XMLHttpRequest(); 344 | req.open('GET', url, true); 345 | req.setRequestHeader('Accept', 'application/json'); 346 | req.setRequestHeader('Authorization', 'bearer ' + kc.token); 347 | 348 | var promise = createPromise(); 349 | 350 | req.onreadystatechange = function () { 351 | if (req.readyState == 4) { 352 | if (req.status == 200) { 353 | kc.profile = JSON.parse(req.responseText); 354 | promise.setSuccess(kc.profile); 355 | } else { 356 | promise.setError(); 357 | } 358 | } 359 | }; 360 | 361 | req.send(); 362 | 363 | return promise.promise; 364 | }; 365 | 366 | kc.loadUserInfo = function() { 367 | var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; 368 | var req = new XMLHttpRequest(); 369 | req.open('GET', url, true); 370 | req.setRequestHeader('Accept', 'application/json'); 371 | req.setRequestHeader('Authorization', 'bearer ' + kc.token); 372 | 373 | var promise = createPromise(); 374 | 375 | req.onreadystatechange = function () { 376 | if (req.readyState == 4) { 377 | if (req.status == 200) { 378 | kc.userInfo = JSON.parse(req.responseText); 379 | promise.setSuccess(kc.userInfo); 380 | } else { 381 | promise.setError(); 382 | } 383 | } 384 | }; 385 | 386 | req.send(); 387 | 388 | return promise.promise; 389 | }; 390 | 391 | kc.isTokenExpired = function(minValidity) { 392 | if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) { 393 | throw 'Not authenticated'; 394 | } 395 | 396 | if (kc.timeSkew == null) { 397 | console.info('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set'); 398 | return true; 399 | } 400 | 401 | var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew; 402 | if (minValidity) { 403 | expiresIn -= minValidity; 404 | } 405 | return expiresIn < 0; 406 | }; 407 | 408 | kc.updateToken = function(minValidity) { 409 | var promise = createPromise(); 410 | 411 | if (!kc.refreshToken) { 412 | promise.setError(); 413 | return promise.promise; 414 | } 415 | 416 | minValidity = minValidity || 5; 417 | 418 | var exec = function() { 419 | var refreshToken = false; 420 | if (minValidity == -1) { 421 | refreshToken = true; 422 | console.info('[KEYCLOAK] Refreshing token: forced refresh'); 423 | } else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) { 424 | refreshToken = true; 425 | console.info('[KEYCLOAK] Refreshing token: token expired'); 426 | } 427 | 428 | if (!refreshToken) { 429 | promise.setSuccess(false); 430 | } else { 431 | var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; 432 | var url = getRealmUrl() + '/protocol/openid-connect/token'; 433 | 434 | refreshQueue.push(promise); 435 | 436 | if (refreshQueue.length == 1) { 437 | var req = new XMLHttpRequest(); 438 | req.open('POST', url, true); 439 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 440 | req.withCredentials = true; 441 | 442 | if (kc.clientId && kc.clientSecret) { 443 | req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); 444 | } else { 445 | params += '&client_id=' + encodeURIComponent(kc.clientId); 446 | } 447 | 448 | var timeLocal = new Date().getTime(); 449 | 450 | req.onreadystatechange = function () { 451 | if (req.readyState == 4) { 452 | if (req.status == 200) { 453 | console.info('[KEYCLOAK] Token refreshed'); 454 | 455 | timeLocal = (timeLocal + new Date().getTime()) / 2; 456 | 457 | var tokenResponse = JSON.parse(req.responseText); 458 | 459 | setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal); 460 | 461 | kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); 462 | for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { 463 | p.setSuccess(true); 464 | } 465 | } else { 466 | console.warn('[KEYCLOAK] Failed to refresh token'); 467 | 468 | kc.onAuthRefreshError && kc.onAuthRefreshError(); 469 | for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { 470 | p.setError(true); 471 | } 472 | } 473 | } 474 | }; 475 | 476 | req.send(params); 477 | } 478 | } 479 | }; 480 | 481 | if (loginIframe.enable) { 482 | var iframePromise = checkLoginIframe(); 483 | iframePromise.success(function() { 484 | exec(); 485 | }).error(function() { 486 | promise.setError(); 487 | }); 488 | } else { 489 | exec(); 490 | } 491 | 492 | return promise.promise; 493 | }; 494 | 495 | kc.clearToken = function() { 496 | if (kc.token) { 497 | setToken(null, null, null); 498 | kc.onAuthLogout && kc.onAuthLogout(); 499 | if (kc.loginRequired) { 500 | kc.login(); 501 | } 502 | } 503 | }; 504 | 505 | function getRealmUrl() { 506 | if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { 507 | return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); 508 | } else { 509 | return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); 510 | } 511 | } 512 | 513 | function getOrigin() { 514 | if (!window.location.origin) { 515 | return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); 516 | } else { 517 | return window.location.origin; 518 | } 519 | } 520 | 521 | function processCallback(oauth, promise) { 522 | var code = oauth.code; 523 | var error = oauth.error; 524 | var prompt = oauth.prompt; 525 | 526 | var timeLocal = new Date().getTime(); 527 | 528 | if (error) { 529 | if (prompt != 'none') { 530 | var errorData = { error: error, error_description: oauth.error_description }; 531 | kc.onAuthError && kc.onAuthError(errorData); 532 | promise && promise.setError(errorData); 533 | } else { 534 | promise && promise.setSuccess(); 535 | } 536 | return; 537 | } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) { 538 | authSuccess(oauth.access_token, null, oauth.id_token, true); 539 | } 540 | 541 | if ((kc.flow != 'implicit') && code) { 542 | var params = 'code=' + code + '&grant_type=authorization_code'; 543 | var url = getRealmUrl() + '/protocol/openid-connect/token'; 544 | 545 | var req = new XMLHttpRequest(); 546 | req.open('POST', url, true); 547 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 548 | 549 | if (kc.clientId && kc.clientSecret) { 550 | req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); 551 | } else { 552 | params += '&client_id=' + encodeURIComponent(kc.clientId); 553 | } 554 | 555 | params += '&redirect_uri=' + oauth.redirectUri; 556 | 557 | req.withCredentials = true; 558 | 559 | req.onreadystatechange = function() { 560 | if (req.readyState == 4) { 561 | if (req.status == 200) { 562 | 563 | var tokenResponse = JSON.parse(req.responseText); 564 | authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard'); 565 | } else { 566 | kc.onAuthError && kc.onAuthError(); 567 | promise && promise.setError(); 568 | } 569 | } 570 | }; 571 | 572 | req.send(params); 573 | } 574 | 575 | function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) { 576 | timeLocal = (timeLocal + new Date().getTime()) / 2; 577 | 578 | setToken(accessToken, refreshToken, idToken, timeLocal); 579 | 580 | if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || 581 | (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) || 582 | (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) { 583 | 584 | console.info('[KEYCLOAK] Invalid nonce, clearing token'); 585 | kc.clearToken(); 586 | promise && promise.setError(); 587 | } else { 588 | if (fulfillPromise) { 589 | kc.onAuthSuccess && kc.onAuthSuccess(); 590 | promise && promise.setSuccess(); 591 | } 592 | } 593 | } 594 | 595 | } 596 | 597 | function loadConfig(url) { 598 | var promise = createPromise(); 599 | var configUrl; 600 | 601 | if (!config) { 602 | configUrl = 'keycloak.json'; 603 | } else if (typeof config === 'string') { 604 | configUrl = config; 605 | } 606 | 607 | if (configUrl) { 608 | var req = new XMLHttpRequest(); 609 | req.open('GET', configUrl, true); 610 | req.setRequestHeader('Accept', 'application/json'); 611 | 612 | req.onreadystatechange = function () { 613 | if (req.readyState == 4) { 614 | if (req.status == 200 || fileLoaded(req)) { 615 | var config = JSON.parse(req.responseText); 616 | 617 | kc.authServerUrl = config['auth-server-url']; 618 | kc.realm = config['realm']; 619 | kc.clientId = config['resource']; 620 | kc.clientSecret = (config['credentials'] || {})['secret']; 621 | 622 | promise.setSuccess(); 623 | } else { 624 | promise.setError(); 625 | } 626 | } 627 | }; 628 | 629 | req.send(); 630 | } else { 631 | if (!config['url']) { 632 | var scripts = document.getElementsByTagName('script'); 633 | for (var i = 0; i < scripts.length; i++) { 634 | if (scripts[i].src.match(/.*keycloak\.js/)) { 635 | config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); 636 | break; 637 | } 638 | } 639 | } 640 | 641 | if (!config.realm) { 642 | throw 'realm missing'; 643 | } 644 | 645 | if (!config.clientId) { 646 | throw 'clientId missing'; 647 | } 648 | 649 | kc.authServerUrl = config.url; 650 | kc.realm = config.realm; 651 | kc.clientId = config.clientId; 652 | kc.clientSecret = (config.credentials || {}).secret; 653 | 654 | promise.setSuccess(); 655 | } 656 | 657 | return promise.promise; 658 | } 659 | 660 | function fileLoaded(xhr) { 661 | return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:'); 662 | } 663 | 664 | function setToken(token, refreshToken, idToken, timeLocal) { 665 | if (kc.tokenTimeoutHandle) { 666 | clearTimeout(kc.tokenTimeoutHandle); 667 | kc.tokenTimeoutHandle = null; 668 | } 669 | 670 | if (refreshToken) { 671 | kc.refreshToken = refreshToken; 672 | kc.refreshTokenParsed = decodeToken(refreshToken); 673 | } else { 674 | delete kc.refreshToken; 675 | delete kc.refreshTokenParsed; 676 | } 677 | 678 | if (idToken) { 679 | kc.idToken = idToken; 680 | kc.idTokenParsed = decodeToken(idToken); 681 | } else { 682 | delete kc.idToken; 683 | delete kc.idTokenParsed; 684 | } 685 | 686 | if (token) { 687 | kc.token = token; 688 | kc.tokenParsed = decodeToken(token); 689 | kc.sessionId = kc.tokenParsed.session_state; 690 | kc.authenticated = true; 691 | kc.subject = kc.tokenParsed.sub; 692 | kc.realmAccess = kc.tokenParsed.realm_access; 693 | kc.resourceAccess = kc.tokenParsed.resource_access; 694 | 695 | if (timeLocal) { 696 | kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; 697 | } 698 | 699 | if (kc.timeSkew != null) { 700 | console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds'); 701 | 702 | if (kc.onTokenExpired) { 703 | var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000; 704 | console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s'); 705 | if (expiresIn <= 0) { 706 | kc.onTokenExpired(); 707 | } else { 708 | kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn); 709 | } 710 | } 711 | } 712 | } else { 713 | delete kc.token; 714 | delete kc.tokenParsed; 715 | delete kc.subject; 716 | delete kc.realmAccess; 717 | delete kc.resourceAccess; 718 | 719 | kc.authenticated = false; 720 | } 721 | } 722 | 723 | function decodeToken(str) { 724 | str = str.split('.')[1]; 725 | 726 | str = str.replace('/-/g', '+'); 727 | str = str.replace('/_/g', '/'); 728 | switch (str.length % 4) 729 | { 730 | case 0: 731 | break; 732 | case 2: 733 | str += '=='; 734 | break; 735 | case 3: 736 | str += '='; 737 | break; 738 | default: 739 | throw 'Invalid token'; 740 | } 741 | 742 | str = (str + '===').slice(0, str.length + (str.length % 4)); 743 | str = str.replace(/-/g, '+').replace(/_/g, '/'); 744 | 745 | str = decodeURIComponent(escape(atob(str))); 746 | 747 | str = JSON.parse(str); 748 | return str; 749 | } 750 | 751 | function createUUID() { 752 | var s = []; 753 | var hexDigits = '0123456789abcdef'; 754 | for (var i = 0; i < 36; i++) { 755 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); 756 | } 757 | s[14] = '4'; 758 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); 759 | s[8] = s[13] = s[18] = s[23] = '-'; 760 | var uuid = s.join(''); 761 | return uuid; 762 | } 763 | 764 | kc.callback_id = 0; 765 | 766 | function parseCallback(url) { 767 | var oauth = new CallbackParser(url, kc.responseMode).parseUri(); 768 | var oauthState = callbackStorage.get(oauth.state); 769 | 770 | if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) { 771 | oauth.redirectUri = oauthState.redirectUri; 772 | oauth.storedNonce = oauthState.nonce; 773 | oauth.prompt = oauthState.prompt; 774 | 775 | if (oauth.fragment) { 776 | oauth.newUrl += '#' + oauth.fragment; 777 | } 778 | 779 | return oauth; 780 | } 781 | } 782 | 783 | function createPromise() { 784 | var p = { 785 | setSuccess: function(result) { 786 | p.success = true; 787 | p.result = result; 788 | if (p.successCallback) { 789 | p.successCallback(result); 790 | } 791 | }, 792 | 793 | setError: function(result) { 794 | p.error = true; 795 | p.result = result; 796 | if (p.errorCallback) { 797 | p.errorCallback(result); 798 | } 799 | }, 800 | 801 | promise: { 802 | success: function(callback) { 803 | if (p.success) { 804 | callback(p.result); 805 | } else if (!p.error) { 806 | p.successCallback = callback; 807 | } 808 | return p.promise; 809 | }, 810 | error: function(callback) { 811 | if (p.error) { 812 | callback(p.result); 813 | } else if (!p.success) { 814 | p.errorCallback = callback; 815 | } 816 | return p.promise; 817 | } 818 | } 819 | }; 820 | return p; 821 | } 822 | 823 | function setupCheckLoginIframe() { 824 | var promise = createPromise(); 825 | 826 | if (!loginIframe.enable) { 827 | promise.setSuccess(); 828 | return promise.promise; 829 | } 830 | 831 | if (loginIframe.iframe) { 832 | promise.setSuccess(); 833 | return promise.promise; 834 | } 835 | 836 | var iframe = document.createElement('iframe'); 837 | loginIframe.iframe = iframe; 838 | 839 | iframe.onload = function() { 840 | var realmUrl = getRealmUrl(); 841 | if (realmUrl.charAt(0) === '/') { 842 | loginIframe.iframeOrigin = getOrigin(); 843 | } else { 844 | loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); 845 | } 846 | promise.setSuccess(); 847 | 848 | setTimeout(check, loginIframe.interval * 1000); 849 | }; 850 | 851 | var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; 852 | if (kc.iframeVersion) { 853 | src = src + '?version=' + kc.iframeVersion; 854 | } 855 | 856 | iframe.setAttribute('src', src ); 857 | iframe.setAttribute('title', 'keycloak-session-iframe' ); 858 | iframe.style.display = 'none'; 859 | document.body.appendChild(iframe); 860 | 861 | var messageCallback = function(event) { 862 | if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) { 863 | return; 864 | } 865 | 866 | if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) { 867 | return; 868 | } 869 | 870 | 871 | if (event.data != 'unchanged') { 872 | kc.clearToken(); 873 | } 874 | 875 | var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length); 876 | 877 | for (var i = callbacks.length - 1; i >= 0; --i) { 878 | var promise = callbacks[i]; 879 | if (event.data == 'unchanged') { 880 | promise.setSuccess(); 881 | } else { 882 | promise.setError(); 883 | } 884 | } 885 | }; 886 | 887 | window.addEventListener('message', messageCallback, false); 888 | 889 | var check = function() { 890 | checkLoginIframe(); 891 | if (kc.token) { 892 | setTimeout(check, loginIframe.interval * 1000); 893 | } 894 | }; 895 | 896 | return promise.promise; 897 | } 898 | 899 | function checkLoginIframe() { 900 | var promise = createPromise(); 901 | 902 | if (loginIframe.iframe && loginIframe.iframeOrigin ) { 903 | var msg = kc.clientId + ' ' + kc.sessionId; 904 | loginIframe.callbackList.push(promise); 905 | var origin = loginIframe.iframeOrigin; 906 | if (loginIframe.callbackList.length == 1) { 907 | loginIframe.iframe.contentWindow.postMessage(msg, origin); 908 | } 909 | } else { 910 | promise.setSuccess(); 911 | } 912 | 913 | return promise.promise; 914 | } 915 | 916 | function loadAdapter(type) { 917 | if (!type || type == 'default') { 918 | return { 919 | login: function(options) { 920 | window.location.href = kc.createLoginUrl(options); 921 | return createPromise().promise; 922 | }, 923 | 924 | logout: function(options) { 925 | window.location.href = kc.createLogoutUrl(options); 926 | return createPromise().promise; 927 | }, 928 | 929 | register: function(options) { 930 | window.location.href = kc.createRegisterUrl(options); 931 | return createPromise().promise; 932 | }, 933 | 934 | accountManagement : function() { 935 | window.location.href = kc.createAccountUrl(); 936 | return createPromise().promise; 937 | }, 938 | 939 | redirectUri: function(options, encodeHash) { 940 | if (arguments.length == 1) { 941 | encodeHash = true; 942 | } 943 | 944 | if (options && options.redirectUri) { 945 | return options.redirectUri; 946 | } else if (kc.redirectUri) { 947 | return kc.redirectUri; 948 | } else { 949 | var redirectUri = location.href; 950 | if (location.hash && encodeHash) { 951 | redirectUri = redirectUri.substring(0, location.href.indexOf('#')); 952 | redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); 953 | } 954 | return redirectUri; 955 | } 956 | } 957 | }; 958 | } 959 | 960 | if (type == 'cordova') { 961 | loginIframe.enable = false; 962 | var cordovaOpenWindowWrapper = function(loginUrl, target, options) { 963 | if (window.cordova && window.cordova.InAppBrowser) { 964 | // Use inappbrowser for IOS and Android if available 965 | return window.cordova.InAppBrowser.open(loginUrl, target, options); 966 | } else { 967 | return window.open(loginUrl, target, options); 968 | } 969 | }; 970 | return { 971 | login: function(options) { 972 | var promise = createPromise(); 973 | 974 | var o = 'location=no'; 975 | if (options && options.prompt == 'none') { 976 | o += ',hidden=yes'; 977 | } 978 | 979 | var loginUrl = kc.createLoginUrl(options); 980 | var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o); 981 | var completed = false; 982 | 983 | ref.addEventListener('loadstart', function(event) { 984 | if (event.url.indexOf('http://localhost') == 0) { 985 | var callback = parseCallback(event.url); 986 | processCallback(callback, promise); 987 | ref.close(); 988 | completed = true; 989 | } 990 | }); 991 | 992 | ref.addEventListener('loaderror', function(event) { 993 | if (!completed) { 994 | if (event.url.indexOf('http://localhost') == 0) { 995 | var callback = parseCallback(event.url); 996 | processCallback(callback, promise); 997 | ref.close(); 998 | completed = true; 999 | } else { 1000 | promise.setError(); 1001 | ref.close(); 1002 | } 1003 | } 1004 | }); 1005 | 1006 | return promise.promise; 1007 | }, 1008 | 1009 | logout: function(options) { 1010 | var promise = createPromise(); 1011 | 1012 | var logoutUrl = kc.createLogoutUrl(options); 1013 | var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); 1014 | 1015 | var error; 1016 | 1017 | ref.addEventListener('loadstart', function(event) { 1018 | if (event.url.indexOf('http://localhost') == 0) { 1019 | ref.close(); 1020 | } 1021 | }); 1022 | 1023 | ref.addEventListener('loaderror', function(event) { 1024 | if (event.url.indexOf('http://localhost') == 0) { 1025 | ref.close(); 1026 | } else { 1027 | error = true; 1028 | ref.close(); 1029 | } 1030 | }); 1031 | 1032 | ref.addEventListener('exit', function(event) { 1033 | if (error) { 1034 | promise.setError(); 1035 | } else { 1036 | kc.clearToken(); 1037 | promise.setSuccess(); 1038 | } 1039 | }); 1040 | 1041 | return promise.promise; 1042 | }, 1043 | 1044 | register : function() { 1045 | var registerUrl = kc.createRegisterUrl(); 1046 | var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no'); 1047 | ref.addEventListener('loadstart', function(event) { 1048 | if (event.url.indexOf('http://localhost') == 0) { 1049 | ref.close(); 1050 | } 1051 | }); 1052 | }, 1053 | 1054 | accountManagement : function() { 1055 | var accountUrl = kc.createAccountUrl(); 1056 | var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); 1057 | ref.addEventListener('loadstart', function(event) { 1058 | if (event.url.indexOf('http://localhost') == 0) { 1059 | ref.close(); 1060 | } 1061 | }); 1062 | }, 1063 | 1064 | redirectUri: function(options) { 1065 | return 'http://localhost'; 1066 | } 1067 | } 1068 | } 1069 | 1070 | throw 'invalid adapter type: ' + type; 1071 | } 1072 | 1073 | var LocalStorage = function() { 1074 | if (!(this instanceof LocalStorage)) { 1075 | return new LocalStorage(); 1076 | } 1077 | 1078 | localStorage.setItem('kc-test', 'test'); 1079 | localStorage.removeItem('kc-test'); 1080 | 1081 | var cs = this; 1082 | 1083 | function clearExpired() { 1084 | var time = new Date().getTime(); 1085 | for (var i = 0; i < localStorage.length; i++) { 1086 | var key = localStorage.key(i); 1087 | if (key && key.indexOf('kc-callback-') == 0) { 1088 | var value = localStorage.getItem(key); 1089 | if (value) { 1090 | try { 1091 | var expires = JSON.parse(value).expires; 1092 | if (!expires || expires < time) { 1093 | localStorage.removeItem(key); 1094 | } 1095 | } catch (err) { 1096 | localStorage.removeItem(key); 1097 | } 1098 | } 1099 | } 1100 | } 1101 | } 1102 | 1103 | cs.get = function(state) { 1104 | if (!state) { 1105 | return; 1106 | } 1107 | 1108 | var key = 'kc-callback-' + state; 1109 | var value = localStorage.getItem(key); 1110 | if (value) { 1111 | localStorage.removeItem(key); 1112 | value = JSON.parse(value); 1113 | } 1114 | 1115 | clearExpired(); 1116 | return value; 1117 | }; 1118 | 1119 | cs.add = function(state) { 1120 | clearExpired(); 1121 | 1122 | var key = 'kc-callback-' + state.state; 1123 | state.expires = new Date().getTime() + (60 * 60 * 1000); 1124 | localStorage.setItem(key, JSON.stringify(state)); 1125 | }; 1126 | }; 1127 | 1128 | var CookieStorage = function() { 1129 | if (!(this instanceof CookieStorage)) { 1130 | return new CookieStorage(); 1131 | } 1132 | 1133 | var cs = this; 1134 | 1135 | cs.get = function(state) { 1136 | if (!state) { 1137 | return; 1138 | } 1139 | 1140 | var value = getCookie('kc-callback-' + state); 1141 | setCookie('kc-callback-' + state, '', cookieExpiration(-100)); 1142 | if (value) { 1143 | return JSON.parse(value); 1144 | } 1145 | }; 1146 | 1147 | cs.add = function(state) { 1148 | setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60)); 1149 | }; 1150 | 1151 | cs.removeItem = function(key) { 1152 | setCookie(key, '', cookieExpiration(-100)); 1153 | }; 1154 | 1155 | var cookieExpiration = function (minutes) { 1156 | var exp = new Date(); 1157 | exp.setTime(exp.getTime() + (minutes*60*1000)); 1158 | return exp; 1159 | }; 1160 | 1161 | var getCookie = function (key) { 1162 | var name = key + '='; 1163 | var ca = document.cookie.split(';'); 1164 | for (var i = 0; i < ca.length; i++) { 1165 | var c = ca[i]; 1166 | while (c.charAt(0) == ' ') { 1167 | c = c.substring(1); 1168 | } 1169 | if (c.indexOf(name) == 0) { 1170 | return c.substring(name.length, c.length); 1171 | } 1172 | } 1173 | return ''; 1174 | }; 1175 | 1176 | var setCookie = function (key, value, expirationDate) { 1177 | var cookie = key + '=' + value + '; ' 1178 | + 'expires=' + expirationDate.toUTCString() + '; '; 1179 | document.cookie = cookie; 1180 | }; 1181 | }; 1182 | 1183 | function createCallbackStorage() { 1184 | try { 1185 | return new LocalStorage(); 1186 | } catch (err) { 1187 | } 1188 | 1189 | return new CookieStorage(); 1190 | } 1191 | 1192 | var CallbackParser = function(uriToParse, responseMode) { 1193 | if (!(this instanceof CallbackParser)) { 1194 | return new CallbackParser(uriToParse, responseMode); 1195 | } 1196 | var parser = this; 1197 | 1198 | var initialParse = function() { 1199 | var baseUri = null; 1200 | var queryString = null; 1201 | var fragmentString = null; 1202 | 1203 | var questionMarkIndex = uriToParse.indexOf("?"); 1204 | var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1); 1205 | if (questionMarkIndex == -1 && fragmentIndex == -1) { 1206 | baseUri = uriToParse; 1207 | } else if (questionMarkIndex != -1) { 1208 | baseUri = uriToParse.substring(0, questionMarkIndex); 1209 | queryString = uriToParse.substring(questionMarkIndex + 1); 1210 | if (fragmentIndex != -1) { 1211 | fragmentIndex = queryString.indexOf("#"); 1212 | fragmentString = queryString.substring(fragmentIndex + 1); 1213 | queryString = queryString.substring(0, fragmentIndex); 1214 | } 1215 | } else { 1216 | baseUri = uriToParse.substring(0, fragmentIndex); 1217 | fragmentString = uriToParse.substring(fragmentIndex + 1); 1218 | } 1219 | 1220 | return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString }; 1221 | }; 1222 | 1223 | var parseParams = function(paramString) { 1224 | var result = {}; 1225 | var params = paramString.split('&'); 1226 | for (var i = 0; i < params.length; i++) { 1227 | var p = params[i].split('='); 1228 | var paramName = decodeURIComponent(p[0]); 1229 | var paramValue = decodeURIComponent(p[1]); 1230 | result[paramName] = paramValue; 1231 | } 1232 | return result; 1233 | }; 1234 | 1235 | var handleQueryParam = function(paramName, paramValue, oauth) { 1236 | var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ]; 1237 | 1238 | for (var i = 0 ; i< supportedOAuthParams.length ; i++) { 1239 | if (paramName === supportedOAuthParams[i]) { 1240 | oauth[paramName] = paramValue; 1241 | return true; 1242 | } 1243 | } 1244 | return false; 1245 | }; 1246 | 1247 | 1248 | parser.parseUri = function() { 1249 | var parsedUri = initialParse(); 1250 | 1251 | var queryParams = {}; 1252 | if (parsedUri.queryString) { 1253 | queryParams = parseParams(parsedUri.queryString); 1254 | } 1255 | 1256 | var oauth = { newUrl: parsedUri.baseUri }; 1257 | for (var param in queryParams) { 1258 | switch (param) { 1259 | case 'redirect_fragment': 1260 | oauth.fragment = queryParams[param]; 1261 | break; 1262 | default: 1263 | if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { 1264 | oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); 1265 | } 1266 | break; 1267 | } 1268 | } 1269 | 1270 | if (responseMode === 'fragment') { 1271 | var fragmentParams = {}; 1272 | if (parsedUri.fragmentString) { 1273 | fragmentParams = parseParams(parsedUri.fragmentString); 1274 | } 1275 | for (var param in fragmentParams) { 1276 | oauth[param] = fragmentParams[param]; 1277 | } 1278 | } 1279 | 1280 | return oauth; 1281 | }; 1282 | }; 1283 | 1284 | }; 1285 | 1286 | if ( 'object' === "object" && module && 'object' === "object" ) { 1287 | module.exports = Keycloak; 1288 | } else { 1289 | window.Keycloak = Keycloak; 1290 | 1291 | if ( typeof undefined === "function" && undefined.amd ) { 1292 | undefined( "keycloak", [], function () { return Keycloak; } ); 1293 | } 1294 | } 1295 | })( window ); 1296 | }); 1297 | 1298 | /* */ 1299 | var installed = false; 1300 | 1301 | var VueKeyCloak = function VueKeyCloak () {}; 1302 | 1303 | VueKeyCloak.install = function (Vue, options) { 1304 | if ( options === void 0 ) options = { 1305 | keycloakOptions: {}, 1306 | keycloakInitOptions: {}, 1307 | refreshTime: 10 1308 | }; 1309 | 1310 | if (installed) { return } 1311 | installed = true; 1312 | 1313 | var keycloak$$1 = keycloak(options.keycloakOptions); 1314 | 1315 | var watch = new Vue({ 1316 | data: function data () { 1317 | return { 1318 | ready: false, 1319 | authenticated: false, 1320 | user: null, 1321 | token: null, 1322 | resourceAccess: null 1323 | } 1324 | } 1325 | }); 1326 | 1327 | keycloak$$1.init(options.keycloakInitOptions).success(function (isAuthenticated) { 1328 | updateWatchVariables(isAuthenticated).then(function () { 1329 | watch.ready = true; 1330 | }); 1331 | 1332 | if (isAuthenticated) { 1333 | setInterval(function () { 1334 | keycloak$$1.updateToken(options.refreshTime + 2) 1335 | .success(function (refreshed) { 1336 | if (refreshed) { updateWatchVariables(true); } 1337 | }); 1338 | }, options.refreshTime * 1000); 1339 | } 1340 | }); 1341 | 1342 | function updateWatchVariables (isAuthenticated) { 1343 | if ( isAuthenticated === void 0 ) isAuthenticated = false; 1344 | 1345 | watch.authenticated = isAuthenticated; 1346 | 1347 | if (isAuthenticated) { 1348 | watch.token = keycloak$$1.token; 1349 | watch.resourceAccess = keycloak$$1.resourceAccess; 1350 | return new Promise(function (resolve, reject) { 1351 | keycloak$$1.loadUserProfile().success(function (user) { 1352 | watch.user = user; 1353 | resolve(); 1354 | }); 1355 | }) 1356 | } else { 1357 | return Promise.resolve() 1358 | } 1359 | } 1360 | 1361 | Object.defineProperty(Vue.prototype, '$keycloak', { 1362 | get: function get () { 1363 | keycloak$$1.ready = watch.ready; 1364 | keycloak$$1.user = watch.user; 1365 | return keycloak$$1 1366 | } 1367 | }); 1368 | }; 1369 | 1370 | VueKeyCloak.version = '0.0.11'; 1371 | 1372 | return VueKeyCloak; 1373 | 1374 | }))); 1375 | --------------------------------------------------------------------------------